larapilot.v5.0.4

From product idea to reviewed Laravel code.

Spec-driven workflow for Laravel — integrated with Laravel Boost. You describe the product; a squad of thirty personas turns it into a PRD, a backlog, plans, code, and tests — one story at a time, through twenty-nine /larapilot-* skills you run in your editor. Your Laravel app then shows all of it at /larapilot: board, PRD, plan, mockups, Git, and usage, in the browser.

The agent proposes. You approve what ships. Human-in-the-loop, always.

The Board page of the Larapilot dashboard: sixteen user stories of a demo app in five columns, from TODO to DONE
The board of Fernway, a demo app built with Larapilot. Take the tour

Install in 3 commands

Add Larapilot to your Laravel app, scaffold the .larapilot/ workspace, and publish the /larapilot-* skills via Laravel Boost.

Terminal
  1. composer require andreapollastri/larapilot --dev
  2. php artisan larapilot:install
  3. php artisan boost:install
Check that it worked
  1. php artisan larapilot:doctor --human
Open the dashboard
  1. php artisan serve
  2. http://127.0.0.1:8000/larapilot

The dashboard is part of your Laravel app: larapilot:install turns it on, and the app you already run serves it at /larapilot, with no other service to start, no frontend build, and no account. On Herd or Valet open http://your-app.test/larapilot, on Sail http://localhost/larapilot. Keep it in a browser tab: each skill writes to .larapilot/, and the next reload shows it. It answers in local, development, testing, and staging, never in production. Every page.

Then open your editor in the project and type /larapilot-: the skills are listed. Pick the one for your situation from First run.

Requires: PHP 8.1+ (8.2+ recommended) · Laravel 10.49+ · 11.45.3+ · 12+ · 13+ · Laravel Boost ^1 or ^2 (Composer picks the latest compatible release) · MCP-capable editor/agent (Claude Code, VS Code, Cursor, etc.)

Still on Laravel 10 or 11?

On Laravel 10/11 the MCP stack uses illuminate/json-schema — stay on a recent framework patch (Laravel 11.47+ recommended on 11.x).

Laravel 10 and 11 are past their security-fix window. Composer 2.9+ refuses every laravel/framework 10.x/11.x release because open advisories have no patched line (fixes shipped in Laravel 12.60+ / 13). Larapilot CI still runs those majors by ignoring only laravel/framework advisories on the 10/11 jobs. Apps still on 10/11 that fail composer update with affected by security advisories need the same ignore (composer config --json policy.advisories.ignore '["laravel/framework"]') or should upgrade to Laravel 12+.

larapilot:install also scaffolds Larastan (level 5+) and Laravel Pint — phpstan.neon.dist, pint.json, Composer scripts, and dev dependencies. php artisan larapilot:quality runs both; the agent runs it before every task is marked done, and you run it in CI. See Code quality.

Already on Boost without a fresh install? Run php artisan boost:update --discover once to pick up new skills.

Connect the MCP servers

boost:install usually registers them for you. If your editor does not list a larapilot server, add both by hand — Boost brings Laravel context (docs search, schema, Tinker), Larapilot brings the workflow (backlog, spec, diagnostics, read-only Artisan):

.mcp.json — or your editor's MCP settings
{
  "mcpServers": {
    "laravel-boost": { "command": "php", "args": ["artisan", "boost:mcp"] },
    "larapilot":     { "command": "php", "args": ["artisan", "mcp:start", "larapilot"] }
  }
}

Check the install at any time with php artisan larapilot:doctor (add --human for a table instead of JSON).

Upgrade

After a new Larapilot release — pull the package with Composer, then refresh project assets with Artisan. Details: Upgrade.

Terminal
  1. composer update andreapollastri/larapilot laravel/boost --with-dependencies
  2. php artisan larapilot:update

Verify with php artisan larapilot:doctor. larapilot:update also bumps laravel/boost to latest stable and republishes skills. Runtime-only refresh: php artisan larapilot:update --skip-boost.

A new major needs its constraint. composer update stays inside the major your composer.json names. From 4.x to 5.0, replace step 1 with composer require andreapollastri/larapilot:^5.0 --dev --with-all-dependencies — see Version 5 vs version 4.

First run

One command starts the project. Which one depends on what already exists.

Your project Type in the editor Walkthrough
A new idea — app, site, or Laravel package /larapilot-inception "your idea" New product · Package
A Laravel app already in production, built without Larapilot /larapilot-adopt Adopt existing app
A legacy system you are rewriting in Laravel /larapilot-inception with a snapshot in .larapilot/legacy/ Legacy porting
A product Larapilot already knows — it has a PRD and a backlog /larapilot-triage "the request" Bug or feature? · every other entry point

Each of the first three ends with a PRD in .larapilot/docs/PRD.md. From there the loop is always the same: /larapilot-spec builds the backlog, then plan → implement → review for each story.

Ten words, one loop

Larapilot turns a conversation into files in your repository, and those files into reviewed code. These are the words the rest of this page uses.

Word What it is Where it lives
Skill A slash command you type in the editor, such as /larapilot-plan US-001. It runs a guided conversation and saves the result. All skills Published into your editor by Laravel Boost
Persona A named point of view inside a skill: 💎 Mark on scope, 🔎 Tom on requirements, 🔐 Lars on security. Lenses on the same work, not separate agents. The squad They speak in chat as 💎 Mark:
PRD Product Requirements Document — what the product promises: users, journeys, requirements, quality targets, scope, decisions. One per project. The PRD .larapilot/docs/PRD.md
Journey J-001 One way a user gets value from the product, from the trigger to the result. Stories are cut from journeys Inside the PRD
Requirement FR-001 One promise, with a named actor and Done means bullets a tester can verify. Tagged Must, Should, Could, or Won't (MoSCoW) Inside the PRD
Quality target NFR-001 A non-functional requirement: a number and the way to verify it, such as p95 under 300 ms, checked by k6 in CI Inside the PRD
Story US-001 A spec: one demonstrable piece of the product, with acceptance criteria. It names the journey and requirements it delivers .larapilot/specs/US-001.yaml, listed in backlog.yaml
Plan & tasks The technical plan of one story, cut into TASK-01, TASK-02… Each task ends in one commit .larapilot/plans/US-001-plan.yaml
Status TODO → PLANNED → IN PROGRESS → REVIEW → DONE. Only skills move a story forward; only you move it to DONE. Workflow backlog.yaml
Runtime packs The rule files skills read before they act. You never edit them; larapilot:update refreshes them .larapilot/runtime-*.md

Everything lives in .larapilot/ and is committed with the code. The next session — yours, a colleague's, or another agent's — starts from the same truth. Every file, explained. You read those files on the dashboard your Laravel app serves at /larapilot, not in YAML.

The dashboard, on a real app

Every picture on this site comes from Fernway, a class-booking app for independent gyms built for these pages with Larapilot itself: a PRD, sixteen stories in five epics, one release shipped and one under way, three developers and a product owner, five weeks of commits. The pages are what /larapilot serves on it — nothing redrawn. Click a picture to see it full size.

The Board: sixteen stories by status, with points, priority, task progress, and merge commits
Board. Sixteen stories by status: three to do, two planned, two in progress, one waiting for review with a blocking comment, eight done with the merge that closed each one. More
A spec in review: header, user story, acceptance criteria, and the mockup that covers it
Spec detail. US-006 in review, as the spec skill wrote it: header, story, acceptance criteria, then the mockup of the full class, the plan, the tasks, and the comments of the team. More
The Plan: milestones by release and a Gantt of epics and specs with a today marker
Plan. Release 0.1.0 shipped, so its milestone is done; 0.2.0 is on track for 16 October, with the waitlist in review and two stories under way today. More
The Design gallery: live previews of the mockup screens of the member app
Design. The member booking flow drawn by /larapilot-design, every screen a live preview tagged with the stories it covers. More
Usage: estimated hours against build time for every delivered spec
Usage. Lucille's ledger and Estimate vs build: the hours each delivered story was planned at, against the time it was in progress — read from the backlog, not logged by hand. More
Economics: the short answer on price, margin per customer, and break-even
Economics. Fernway priced as a subscription by a UK limited company: what the build costs, what one customer leaves each month, how many cover the bills. More
The Database diagram: seventeen tables with their columns and foreign keys
Database. The seventeen tables of the app as a diagram, foreign keys drawn column to column — laid out on the server, plain SVG, also as a PDF. More
The Logs page with an exception opened on the frames of the application
Logs. An exception opened on where it was thrown in the application; the nineteen frames of the framework stay one click away. More
A booking confirmation kept by the Laravel page: headers, mailable, request, and message
Laravel. A booking confirmation as it left the app: who it went to, the mailable that built it, the request that sent it, and the message in a frame that runs no script. More
The Git history: Gitflow branches, pull request merges, and commits tagged with their stories
Git. Gitflow as the history draws it: stories merged into release/0.2.0 by pull request, two feature branches under way, every commit tagged with its story. More

Light, dark, and a phone

The dashboard follows the system theme, or the switch at the bottom of the sidebar, and every page works at the width of a phone.

The Board in dark mode
The board in dark mode.
The Board on a phone
The board on a phone.
A spec on a phone
A spec on a phone.

How to use

You type a /larapilot-* skill in your editor and answer its questions. The skill writes its result to .larapilot/ and moves the story to its next status. You never edit those files by hand, and nothing ships without your review.

A new product — the last three repeat for every story
/larapilot-inception "…" → /larapilot-spec → /larapilot-plan US-XXX → /larapilot-implement US-XXX → /larapilot-review US-XXX

Inception writes the PRD, spec cuts it into stories, and each story is planned, built, and reviewed. Walkthrough.

An app already in production, built without Larapilot
php artisan larapilot:install → /larapilot-adopt → /larapilot-spec → the same loop

/larapilot-adopt reverse-engineers the PRD from the code. Walkthrough.

A product that has a PRD — a request arrives
/larapilot-triage "…" → /larapilot-bug or /larapilot-feature, run for you → the same loop

Triage checks the request against what the PRD and the backlog promised, states the verdict in one line, and runs the right skill in the same turn. Walkthrough.

Follow the loop in the browser. You work in the editor; your Laravel app shows the result at /larapilot. The board moves a story as each skill finishes, the PRD reads as a document with its decisions, the plan is a Gantt with today on it, and the mockups open as a site you click through. Read a spec there before you approve it. The dashboard.

Which skill, when

You have Type
Starting a project
A new product, site, app, or PHP/Laravel package — or a legacy system to rewrite /larapilot-inception "…"
A Laravel app in production, built without Larapilot, no PRD yet /larapilot-adopt
Building, story by story
A PRD and no backlog, or requirements no story covers yet /larapilot-spec
A story that needs mockups before it is planned optional /larapilot-design US-XXX
A story in TODO /larapilot-plan US-XXX
A story in PLANNED /larapilot-implement US-XXX
A story in REVIEW /larapilot-review US-XXX
Several stories to plan and build in one run /larapilot-autopilot US-004 US-005 …
Every story of the release DONE /larapilot-ship
Changing a product that has a PRD
One new capability, or a shipped one that must now behave differently /larapilot-feature "…" — walkthrough
A defect or a regression /larapilot-bug "…" — walkthrough
A request that may be either — a ticket, a client email /larapilot-triage "…" — walkthrough
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 "…" — walkthrough
Around the loop
How the project works: effort, Git, tests, opt-in extras /larapilot-settings
Time and token spend, deadlines, the Gantt /larapilot-usage
Re-plan a project that is already planned: order, estimates, dates /larapilot-schedule
A quote, tax, payback, pricing tiers, a business plan (account FREELANCE or COMPANY) /larapilot-economics
Semver releases on release/x.y.z branches (release_mode=YES) /larapilot-release
A living project handbook (project_docs=YES) /larapilot-project-docs
A team ritual the packaged skills do not cover — a deploy gate, a compliance check /larapilot-custom-skill — walkthrough
Connecting other tools
The frontend in another repository /larapilot-frontend-companion — walkthrough
A client or PM who follows work in Linear, Jira, Asana, Trello, ClickUp, or Monday /larapilot-tracker — walkthrough
Vulnerabilities Aikido found in the repository /larapilot-aikido — see Aikido
Errors the application throws in production /larapilot-error — see Production errors
Dependencies with known vulnerabilities (CVE), or an SBOM to hand over /larapilot-vendor-check — see SBOM, CVE & Checkpoint
A Laravel, a PHP, or a database version to move past — or another database engine /larapilot-laravel-upgrade, /larapilot-php-upgrade, /larapilot-db-upgrade — see Laravel, PHP & DB upgrades
A Backstage developer portal /larapilot-backstage

What happens by default

A fresh install works without any configuration. These are the behaviors you get, and the setting that changes each one.

By default Change it with
One feature/US-XXX-* branch per story, one commit per task. Nothing is pushed git_mode — GITFLOW_PUSH pushes and opens the PR
A story reaches DONE only when you approve it auto_approve
Feature, unit, policy, and API tests with Pest. No browser tests testing — BEST adds Playwright or Dusk
Tokens, hours, and your explicit decisions are recorded lucille · decision_log
Before scope is written, inception asks your consent and searches for products that already do it prior_art
Developer domain docs are written with every task, in English Nothing — always on
Release mode, project handbook, comments, dashboard and API auth, security scan, forges, trackers, notifications are off /larapilot-settings

Settings are stored in .larapilot/config.yaml and committed, so the whole team works in the same mode. Secrets and machine-specific paths stay in .env.

Two MCP servers run beside the skills: Laravel Boost gives the agent Laravel context (docs search, schema, Tinker) and Larapilot gives it the workflow (backlog, specs, diagnostics, read-only Artisan). See Artisan CLI → MCP server.

Larapilot for the whole team, in a browser

Larapilot runs where your agent runs: an editor or a terminal. Not everyone on a product team opens one. Studio puts the same skills, the same backlog, and the same approvals on a server, behind a web page — for the product manager who decides, the developer who builds, and the client who follows.

Studio: a conversation where Claude writes and plans story US-014 with the Larapilot commands, then asks permission to run npm run build; the preview of the branch on the right
Studio. A PM asks for a date filter on the orders. Claude writes US-014 with larapilot:spec-add, plans it with larapilot:spec-plan, implements it on its branch, and asks before rebuilding the assets. On the right, the preview of that branch. From the Studio docs.
Terminal — a fresh Ubuntu 24.04 or 26.04 server
  1. wget -qO- https://raw.githubusercontent.com/andreapollastri/studio/refs/heads/main/installer/install.sh | sudo bash

The wizard asks for the domain, the DNS record to create, the first administrator, and the git provider, then installs the newest release of Studio at studio.<your domain>. Larapilot needs nothing more: install it in the project as the Quickstart says, or let Studio create the repository with it. Everything else is on studio.web.ap.it.

Version 5 vs version 4

Same skills, same artifacts, same slash commands. What changed is how much an agent reads before it starts working — and how sure it is to read the rules of your project, those of your frontend team included.

Context loaded before the work starts

Tokens of skill and runtime files, on a project with default settings

  • v4
  • v5
/larapilot-implementfirst skill of a conversation
43k
15k
−65%
/larapilot-planfirst skill of a conversation
44k
17k
−61%
/larapilot-reviewafter implement, same conversation
38k
2k
−94%
triage → bug → plan → implement → reviewone conversation, five skills
173k
31k
−82%

v4 is what a skill was told to read: its packs, whole, every time. v5 is what larapilot:context lists. Characters ÷ 4; your editor's tokenizer will differ a little, the ratio will not.

What an agent does differently

v4 v5
Finding the rules Reads the index, works out which rows apply, reads every part of each pack, then calls config-show One call — larapilot:context {skill} — answers settings, paths, the facts of the project, and the files to read
Which rules Every value of every setting: three Git modes, three testing bars, every toggle Compiled for the project: the row its settings call for, and no section for a toggle that is off. A rule the agent never reads cannot be applied by mistake
The next skill of the conversation Reads everything again Reads only what is new — a session token says what is already loaded. --fresh after a compaction
Heavy packs — tenancy, CI/CD, integrations, UX, deploy Read up front, whether the spec touches them or not Named with the moment that calls for them, read then
Logging a session to Lucille A pack of about 7k tokens, read by every skill for one line of the ledger The command comes ready in the envelope
spec-list Every spec with its whole body and history Code, title, status, priority, epic, and the PRD ids each spec cites — --full for the rest
The PRD Opened whole, by nearly every skill prd-show: the outline, the blocks of some ids, or one section
One rule, one place Git mode, testing bar, and the delivery rules restated in up to six files Said once and cited; /larapilot-ship is half its size
Zoey's context estimate Character counts added up by the agent A number the CLI computed

Skill by skill

What each skill loads when it is the first of a conversation — its own instructions plus the runtime files it reads. Later skills of the same conversation pay only for what is new.

Skill v4 v5 Change
/larapilot-inception ~42k ~21k −51%
/larapilot-adopt ~45k ~20k −55%
/larapilot-feature ~38k ~14.5k −62%
/larapilot-bug ~37k ~10k −73%
/larapilot-spec ~38k ~13k −66%
/larapilot-plan ~44k ~17k −61%
/larapilot-implement ~43k ~15k −65%
/larapilot-review ~38k ~9.4k −75%
/larapilot-ship ~36k ~12k −67%
/larapilot-design ~30k ~14k −53%
/larapilot-settings ~25k ~10k −60%
/larapilot-triage ~11k ~6k −45%

The frontend companion

Version 4 linked a frontend folder and read the package.json at its root. Version 5 reads the workspace that folder lives in — one app, a monorepo several products share, or one app kept in its own repository and built inside such a monorepo — and the rules its team wrote for coding agents. Same skills, same repo: frontend tasks: what changes is what the agent knows before its first frontend edit.

What the agent knows about the frontend before it writes

Counted in the code of each version's frontend-scan

  • v4
  • v5
Workspace layouts readone app, monorepos, an app built inside one
1
11
+10
Kinds of agent rules readAGENTS.md and the formats of 11 editors
0
12
+12
Verification commands givenbuilt from the workspace, non-interactive
0
9
+9
Stack playbooks, by major versionfor what rules and code leave open
0
4
+4

v4 answered a stack label, five folder checks, and the entrypoints of one package.json. Each v5 number is a list in its scan, not an estimate.

The same repository, scanned by each version

An Angular app kept in a repository of its own and cloned at apps/billing inside the team's Nx monorepo: a project.json and no package.json, a tsconfig that extends the monorepo's, the shared UI library imported from there.

v4 · frontend-scan
{
  "path": "…/acme-frontend/apps/billing",
  "package": null,
  "stack": {
    "configured": null,
    "detected": null,
    "resolved": null
  },
  "tooling": {
    "vite": false, "next": false,
    "nuxt": false, "angular": false
  },
  "structure": { "src": true, "app": false, … },
  "entrypoints": ["src/main.ts"],
  "dependencies": []
}
v5 · frontend-scan
{
  "root": "…/acme-frontend",
  "run_in": "…/acme-frontend",
  "workspace": {
    "location": {
      "source": "ancestor",
      "project_root": "apps/billing"
    },
    "kind": "nx",
    "tool": { "name": "nx", "version": "20.1.0" }
  },
  "target_projects": [{
    "name": "billing",
    "stack": "Angular",
    "framework": { "version": "18.2.0" },
    "git_root": "…/acme-frontend/apps/billing",
    "commands": {
      "test": "CI=true npx nx run billing:test
        --watch=false --browsers=ChromeHeadless",
      "build": "npx nx run billing:build"
    },
    "unavailable": {
      "lint": "TSLint support left the
        Angular CLI in v13 …"
    },
    "observed": [
      "Components: 31 standalone, …",
      "Template control flow: 0 built-in
        blocks, 58 structural directives …"
    ],
    "depends_on": ["ui"]
  }],
  "write_scope": {
    "owned": [{ "name": "billing" }],
    "shared": [{ "name": "ui", "used_by": 6 }]
  },
  "rules": {
    "must_read": [
      { "path": "AGENTS.md" },
      { "path": ".cursor/rules/components.mdc" }
    ]
  },
  "git": { "commits": {
    "pattern": "<type>: {code} TASK-NN <summary>"
  } },
  "playbooks": [
    ".larapilot/runtime-frontend-angular.md"
  ]
}

Trimmed. v4 saw a folder with no stack and nothing to run. v5 finds the monorepo among the parent folders, runs the commands there, commits in the app's own repository, and leaves out the lint target the installed CLI can no longer run.

What the companion does differently

v4 v5
The repository One folder with a package.json at its root Nx (the graph of the workspace's own nx, cached), Angular CLI, pnpm / yarn / npm / bun workspaces, Turborepo, Lerna, Rush, one app — each project with its type, tags, targets, stack, installed version, and dependencies
A monorepo shared with other products Read as one app: any folder could be written The user names this product's projects; libraries are owned, shared (changed only when a task names them), or vendored (never edited)
An app in its own repository, built inside a monorepo Not seen: no stack, no commands, no shared libraries The monorepo is found among the parent folders, or linked with --workspace; commands run there, commits go to the app's repository
The team's agent rules Never read: the editor loads the rules of the Laravel workspace only AGENTS.md at any depth, CLAUDE.md with its imports, Cursor, Copilot, and eight more formats — those of the monorepo around an app too. frontend-rules --file before each write; on code, they win
How the code is written A stack label Measured on the code — NgModule or standalone, *ngIf or @if, decorators or signals, <script setup>, Svelte runes — with recent files as models and a playbook per stack and major version
Checks npm test, whatever the workspace The workspace's own commands: nx run, nx affected, ng test --watch=false with headless Karma, pnpm --filter, … A target the installed CLI can no longer run is left out and named
Tests the team never wrote Not noticed A project with no spec — or generators set to skip them — is a question for the user, recorded in the decision journal
The API client Written by hand A generated client (orval, openapi-generator, ng-openapi-gen, …) is regenerated from the product OpenAPI, never edited
Commits feat(US-XXX): TASK-NN … in every repository The style of the repository's history — types, scopes, language — with the task id in it; task-done finds the commit in the frontend repository
Who builds the UI Larapilot, from the Laravel workspace Larapilot (driven), or the frontend team in its own repository from frontend-brief (handoff)
Machine paths Setting the stack copied the local path into the committed config.yaml Paths live in .env only

The whole flow: External frontend repo.

Workflow hooks

New in v5, and off until you turn it on: your own commands and skills on the transitions of the loop — plan saved, spec started, task done, review, approval, rework, release, ship — written in .larapilot/hooks.yaml and shared through git. A before hook that fails refuses the transition and nothing is written; an after hook is reported and the transition stands.

v4 v5, with hooks=YES
A check of the team — tests, Larastan, npm run build An instruction the agent may forget A before hook of task.done or spec.review: the command runs it and refuses the transition when it fails
A deploy By hand, after the review or the ship report An after hook of spec.approved for staging, of ship or release.shipped for production
A custom skill at the right moment Typed by someone who remembered it A skill: hook: before a transition the command waits until the agent reports it run; after one, the answer tells the agent to run it now
A tool Larapilot does not integrate — Teams, a time tracker, a wiki, n8n A script nobody calls A run: hook, with the event in LARAPILOT_HOOK_* variables and as JSON on stdin
Getting past a gate — Never by the agent: it fixes what the hook reports, or tells you. --force skips no hook; LARAPILOT_HOOKS_ENABLED=false turns them off on one machine

Events, phases, and the rules: Workflow hooks.

Upgrading from 4.x

Terminal
composer require andreapollastri/larapilot:^5.0 --dev --with-all-dependencies
php artisan larapilot:update
php artisan larapilot:doctor

A project installed with version 4 holds ^4.x in its composer.json, so composer update alone stays on 4: the first command raises the constraint to the new major. larapilot:update then republishes the skills, refreshes the runtime, and removes the runtime files v5 no longer ships. Nothing in .larapilot/ has to be migrated: PRD, backlog, plans, decisions, and settings are read as they are. Larapilot under require rather than require-dev? Drop --dev, or Composer moves it. Six things to check:

  • spec-list in your scripts — it answers without the bodies now. Add --full where a script reads body or status_history.
  • Custom skills — they keep working: the old runtime files are still in .larapilot/, re-split by audience. To give one the session cache and the compiled rules, start it with php artisan larapilot:context {its name} --with=delivery-1,dev-docs (Custom skills).
  • /larapilot-boogle is /larapilot-error — one skill for every error tracker. The boogle-* commands still answer under their old names.
  • An external frontend — run /larapilot-frontend-companion once: in a monorepo it asks which projects are this product's, and it reads the team's agent rules from then on.
  • .larapilot/hooks.yaml is new — the update writes it with every example commented out, and hooks stays NO: nothing runs. Commit it, and turn hooks on when the team has written one (Workflow hooks).
  • larapilot:vps-provision is gone — with the provision.sh it generated: drop the call from scripts and CI. A server it set up is not touched by the upgrade, but its prj-* tools are no longer maintained: deploy those projects with the platform the PRD records, through /larapilot-ship.

Step by step — a branch, a dry run, the output to read, the checks as commands, and the way back: Major releases. How the loading works, in detail: What the agent loads — and what it skips.

Use cases

New product

/larapilot-inception

Laravel 13 greenfield — task board with registration, projects, assignable tasks. One sentence in, PRD + backlog + code out.

Your editor
/larapilot-inception "I want a simple team task board: registration, projects, and assignable tasks."

Inception is a conversation, not a questionnaire. It goes through five moments, in this order:

  1. The goal is challenged in plain prose — who has the problem, what changes if it works, how you will know in 90 days, what would make you stop.
  2. Fixed choices are asked with AskQuestion, at most three per round, all skippable. Four rounds always happen: Project Kind, Delivery Target, Business Model, Operations & support.
  3. Prior art — with your consent, Sebastian searches for products and packages that already do it, and you decide: build anyway, adopt, or integrate.
  4. The product is written down — journeys, domain model, requirements with Done means, quality targets, risks.
  5. Ready check and readback — Tom checks the requirements can be tested, Mark reads the decisions back, and only then is the PRD saved.

💎 Mark: Before scope — who has this problem today, and what do they use instead?

You: Small agencies, 2–10 people. They use Trello and lose track of who owns what.

🧭 Jennifer: Then "who owns what" is the product, not the board. How will you know in 90 days that it worked?

You: Three agencies using it daily, and no task without an assignee.

🏢 Benjamin: Riskiest assumption: that they'll leave Trello. A CSV import from Trello lowers that cost — I'll propose it as a Should.

AskQuestion — Round 1 (kind, target, money)
  1. Project Kind: Application · Website · Personal · Package
  2. Delivery Target: MVP · V1 Complete · Full Product · Enterprise
  3. Business Model: Client project · SaaS subscription · E-commerce · Licensed package · Internal tool

You pick: Application, MVP, SaaS subscription.

💎 Mark: A SaaS that ships as an MVP — registration, projects and tasks are Must; the Kanban view and notifications can wait.

💡 Sebastian: Before scope, I would search for what already exists: "open source team task board laravel" and "self-hosted kanban". Nothing in those words identifies you. May I?

AskQuestion — prior art (consent)
  1. Search now · Search with generic terms only · Skip — I know the alternatives · Skip — confidential

You pick: Search now.

💡 Sebastian: Three candidates, written to research/prior-art.md with license, stack, and last release. The two closest are boards with no Laravel code to reuse, and neither answers "who owns what" across projects.

AskQuestion — verdict
  1. Build anyway · Adopt / fork · Integrate as dependency · Not checked

You pick: Build anyway.

💎 Mark: Then the difference leads the backlog: no task without an owner is the first Must.

AskQuestion — Round 2 (operations & support)
  1. Server Management: Managed platform · Self-managed VPS · Kubernetes / cloud · Client infrastructure
  2. Ops Owner: Me / my team · Client team · Managed provider · Shared
  3. Support Window: Best effort · Business hours · Extended hours · 24/7

You pick: Managed platform, Me / my team, Business hours. These three answers price the maintenance retainer in Economics.

AskQuestion — Round 3 (topology, stack & budget)
  1. Frontend Topology: Laravel-coupled · SPA-in-Laravel · API + external frontend
  2. Authenticated UI (in this repo): Laravel Starter Kit · Filament · AdminLTE · Bootstrap 5 · Tailwind CSS · Custom
  3. Budget sensitivity: Tracked · Relaxed

You pick: Laravel-coupled, Laravel Starter Kit, Tracked.

📐 John + ✨ Joe: Coupled Blade/Inertia UI in this repo — Starter Kit + session auth fits Laravel 13 out of the box. Filament stays a Future Phase option for ops.

🔎 Tom: Ready check: nine points of ten. FR-003 says "assign a task" and does not say what happens when the owner leaves the team. I would add: the task returns to the project owner.

You: Yes.

💎 Mark: Before I write anything — task board for small agencies · core journey assign and follow a task · MVP, sold as SaaS · prior art: build anyway · Must: registration, projects, assignment with an owner always set · riskiest assumption: agencies leave Trello · nothing left undecided.

AskQuestion — readback
  1. Write the PRD · Revise

You pick: Write the PRD.

PRD.md (excerpt)
## User Journeys

### J-001: Assign and follow a task _(core journey)_

**Persona:** Agency lead · **Trigger:** client work arrives · **Frequency:** daily
**Steps:** 1. open the project 2. create the task 3. pick the owner 4. the owner sees it on their list
**Success end-state:** the task has exactly one owner and appears on that owner's list
**Failure modes:** the owner left the team · the task has no project
**FRs:** FR-002, FR-003 · **MoSCoW:** Must

## Domain Model

| Entity | What it is | Key states / lifecycle | Relations | Owner persona |
| --- | --- | --- | --- | --- |
| Task | A unit of work | Open → In progress → Done | belongs to Project, has one owner | Agency lead |

## Functional Requirements

### FR-003: Assign a task to a team member

**MoSCoW:** Must · **Journey:** J-001 · **Persona:** Agency lead
**Actor & trigger:** the agency lead, from a project, when work is handed over
**Behavior:** every task has exactly one owner, chosen among the members of its project
**Done means:**
- a task cannot be saved without an owner
- when an owner leaves the team, their open tasks return to the project owner
- the owner sees the task on their list at the next page load
**Out of this FR:** several owners, watchers (Future Phases)
**Depends on:** FR-002 · **Source:** interview

### FR-004: SSO via Google Workspace

**MoSCoW:** Won't

## Non-Functional Requirements

| ID | Category | Target | Applies to | Verified by |
| --- | --- | --- | --- | --- |
| NFR-001 | Performance | p95 < 300 ms on task lists at 5k tasks | J-001 | k6 run in CI |
| NFR-002 | Accessibility | WCAG 2.2 AA | all UI | Lighthouse + axe |

## MVP Scope

**Project Kind:** Application
**Delivery Target:** MVP
**Business Model:** SaaS subscription
**Prior Art:** Build anyway
**Success signal:** three agencies using it daily; no task without an owner

## Risks & Assumptions

**Riskiest assumption:** agencies will leave Trello
**Kill condition:** no agency uses it daily after 90 days

## Technical Architecture

**Server Management:** Managed platform
**Ops Owner:** Me / my team
**Support Window:** Business hours
**Frontend Topology:** Laravel-coupled
**Stack:** Laravel 13 · Laravel Starter Kit · Pest

Every section of the PRD is explained in The PRD.

Your editor
/larapilot-spec

Tom cuts one story per journey. Its acceptance criteria come from the Done means of the requirements it delivers, plus the quality targets that apply. Won't requirements get no story.

backlog.yaml (excerpt)
specs:
  - code: US-001
    title: User Registration
    status: TODO
  - code: US-002
    title: Project Management
    status: TODO
  - code: US-003
    title: Task Assignment
    status: TODO
Your editor — optional
/larapilot-design US-001

Static HTML mockup from the PRD-chosen packaged design system (design-systems/filament/, starter-kit/, bootstrap-5/, tailwind/, or adminlte/). Preview at /mockups/US-001, on the spec detail page, and in the dashboard Design index (/larapilot/design) — navigable walk through every screen, plus a zip of HTML + assets.

Your editor — US-001
/larapilot-plan US-001
/larapilot-implement US-001
/larapilot-review US-001
Prompt Status change What lands
/larapilot-plan US-001 TODO → PLANNED plans/US-001-plan.yaml
/larapilot-implement US-001 PLANNED → REVIEW User model, RegisterController, Blade view, Pest tests, and the domain doc .larapilot/docs/devs/user-registration.md
/larapilot-review US-001 REVIEW → DONE You approve — or send back with feedback
Laravel 13 app (US-001)
database/migrations/xxxx_create_users_table.php
app/Models/User.php
app/Http/Controllers/RegisterController.php
app/Http/Requests/RegisterRequest.php
resources/views/auth/register.blade.php
routes/web.php
tests/Feature/RegistrationTest.php
.larapilot/docs/devs/user-registration.md   # why it is built this way — always English
Story Prompts What gets implemented
US-002 Projects plan → implement → review Project model, CRUD, policies, Pest tests
US-003 Tasks plan → implement → review Task model, assignment, board view, tests
Your editor — optional batch
/larapilot-autopilot US-002 US-003

Chains plan + implement, one spec at a time. Under STANDARD or MAX, plan and implement Phase 1 each run in a fresh sub-agent; this session keeps CLI transitions, questions, and the Robert/Lars review. ECO stays inline. You still run /larapilot-review per spec unless auto_approve is YES.

Your editor — when all MVP stories are DONE
/larapilot-ship
You do
Answer wizard rounds; run spec loop per story; approve reviews
Agent does
PRD → backlog → plans → Laravel code + tests under .larapilot/
Next step
Repeat for US-002, US-003 — then /larapilot-ship

Adopt an existing app

/larapilot-adopt

A 2-year-old Laravel 11 SaaS in production — no PRD, no backlog, built straight from tickets. You want the Larapilot loop from here on, without rewriting anything. The code is the source of truth; adopt reconstructs the product spec from it.

Your editor
composer require andreapollastri/larapilot --dev
php artisan larapilot:install
/larapilot-adopt

No idea to type — the agent reads the repository. Tom, John, Mike and Andrew map the code; Mark frames it as a product; you only answer what the code can't tell them.

🔎 Tom: Scanned the repo. 14 models, 9 policies, 22 routed controllers, 6 queued jobs, a Cashier subscription flow, and a Filament admin panel. I've written every observed behavior into .larapilot/research/codebase-analysis.md with file-path evidence.

💎 Mark: From shipped behavior I can infer the personas (Account owner, Team member, Admin) and ~26 functional requirements. Three things the code doesn't settle:

AskQuestion — Round 1 (gaps the code can't answer)
  1. Delivery target for the reconstructed PRD: MVP · V1 Complete · Full Product · Enterprise
  2. app/Support/LegacyImport/ — still in use? Active feature · One-off migration (mark deprecated) · Unsure
  3. Two half-built controllers behind a feature flag: In scope (document as planned) · Out of scope (note as spike)

You pick: V1 Complete, One-off migration, Out of scope.

📐 John: Architecture, data model and package inventory captured. Andrew flagged 3 spots that fight Laravel idiom — noted as tech-debt candidates, not blockers.

The PRD records Project Origin: Adopted (existing codebase) — downstream skills scope it like Greenfield (Sabrine stays silent, no legacy parity contract).

.larapilot/research/codebase-analysis.md (excerpt)
## Routed surface
| Method | URI | Controller | Auth | Notes |
| --- | --- | --- | --- | --- |
| GET  | /dashboard        | DashboardController@index      | auth, verified | team-scoped |
| POST | /projects         | ProjectController@store       | auth, can:create,Project | |
| POST | /billing/checkout | BillingController@checkout    | auth, can:manage-billing | Cashier |

## Inferred requirements (evidence)
- FR-004 Team members can be invited by email — `InviteController`, `InvitationMail`, `invitations` table
- FR-011 Billing is per-seat monthly — `Team::subscription()`, `config/cashier.php`, `PlanSeeder`
- FR-019 Admins can suspend a team — `Admin/TeamController@suspend`, `Team::$casts['suspended_at']`
PRD.md (excerpt)
**Project Origin:** Adopted (existing codebase)
**Delivery Target:** V1 Complete
**Project Kind:** Application

### In Scope (reconstructed from shipped behavior)
- FR-001…FR-026 — see analysis report for per-FR file evidence

### Known gaps / not reverse-engineered
- Two feature-flagged controllers — spike, out of scope (your call)
- `app/Support/LegacyImport/` — deprecated one-off, do not extend
You do
larapilot:install, then /larapilot-adopt; answer only the gap questions
Agent does
Reads the whole codebase → codebase-analysis.md + a complete PRD via prd-write / validate-prd
Next step
/larapilot-spec to build the backlog, then the normal plan → implement → review loop

Laravel package

/larapilot-inception · Project Kind Package

First-class Composer package workflow — provider, public API, Pest matrix, CI, semver, Packagist/Satis. UI mockups stay off unless the package ships Blade/Livewire/Filament.

Your editor
/larapilot-inception "Laravel package that adds signed webhook delivery with retries and a publishable config."

💎 Mark: Package kind — greenfield or wrapping existing code?

AskQuestion — Package origin & delivery
  1. Project Kind: Application · Website · Personal · Package
  2. Package Origin: New · Existing local · Existing git
  3. Delivery target: MVP · V1 Complete · Full Product

You pick: Package, New, MVP.

👾 Andrew + 📐 John: Namespace, Service Provider, config publish, and public API surface before backlog.

🗄️ Mike: Optional migrations only if the package owns schema; otherwise document host-app tables.

⌨️ Sarah: CI matrix (PHP/Laravel) + release scripts; Artisan doctor/publish commands when useful.

📒 Lucille: Any Packagist / demo deadline? (skippable)

PRD.md (excerpt)
**Project Kind:** Package
**Package Origin:** New
**Delivery Target:** MVP

### Package
**Name:** acme/webhooks
**Distribution:** Packagist
**Consumer install:** composer require acme/webhooks

### FR-001: Publishable config + Service Provider
**MoSCoW:** Must

### FR-002: Signed outbound webhook client with retries
**MoSCoW:** Must

### FR-003: Pest feature tests + GitHub Actions matrix
**MoSCoW:** Must

Then the same loop: /larapilot-spec (package-surface stories first) → plan → implement → review → /larapilot-ship with Packagist/semver checklist. Skip /larapilot-design unless the package ships UI.

You do
Confirm origin, name, distribution, and consumer install mode
Agent does
Package layout, tests, CI YAML (Sarah), docs (Albert), optional migrations (Mike)
Next step
/larapilot-ship — tag, CHANGELOG, publish notes

Legacy porting

/larapilot-inception

15-year PHP ERP in .larapilot/legacy/ — rewrite on Laravel 12 + Filament, zero feature/data loss, phased cutover.

Prep — before inception
.larapilot/legacy/
  SOURCE.md              # repo URL + tag
  schema.sql             # sanitized dump
  app/                   # legacy PHP codebase snapshot
Your editor
/larapilot-inception "Rewrite our legacy ERP to Laravel — keep customers, orders, warehouse, and e-invoicing"

Mark + Sabrine scan legacy/ first, then ask about rewrite approach and scope.

💎 Mark: I see a legacy ERP snapshot in .larapilot/legacy/. Before we dive into architecture — how should we treat it?

AskQuestion — Round 1 (project origin)
  1. Approach: Legacy rewrite · Legacy port · Partial modules only · Reference only · Decide later
  2. Cutover style: Big-bang · Phased by module · Parallel run · Unsure
  3. Data in scope: Full history · Last 24 months · Master data only · Decide in chat

You pick: Legacy rewrite, Phased by module, Full history (orders, invoices, stock movements must migrate).

🔄 Sabrine: I mapped five modules — anagrafica, ordini, magazzino, fatturazione, report. Warehouse and e-invoicing touch 40+ legacy tables and ~12k uploaded PDF/XML files.

AskQuestion — Round 2 (scope & delivery)
  1. Project Kind: Application · Website · Personal · Package
  2. Phase 1 modules: Customers + orders · + Warehouse · + E-invoicing · All five
  3. Delivery target: MVP · V1 Complete · Full Product · Enterprise

You pick: Application, Customers + orders (warehouse and e-invoicing in Phase 2), MVP.

AskQuestion — Round 3 (admin & stack)
  1. Admin panel: Filament · AdminLTE · Laravel Starter Kit · Bootstrap 5 · Custom panel
  2. Multi-tenancy: Single company · Multi-company (row-level) · DB per tenant
  3. Budget sensitivity: Tracked · Relaxed

You pick: Filament, Multi-company (row-level), Tracked.

📐 John: Row-level tenancy fits the legacy company_id pattern. Matt will ETL from MySQL; Sabrine owns assets under /uploads/fatture/.

PRD records Project Origin: Legacy rewrite + parity matrix. Migration specs come first on /larapilot-spec.

PRD.md (excerpt)
**Project Origin:** Legacy rewrite
**Delivery Target:** MVP
**Project Kind:** Application

### In Scope (Phase 1 — MVP)
- FR-001: Customer master data (parity with legacy anagrafica)
- FR-002: Sales orders — create, edit, status workflow
- FR-003: Multi-company access (row-level, legacy company_id)

### Future Phases
- FR-010: Warehouse / stock (legacy magazzino — Phase 2)
- FR-011: Italian e-invoicing SDI (legacy fatturazione — Phase 2)
- FR-012: Accounting reports (legacy report — Phase 3)

### Legacy parity
- Matrix: `.larapilot/research/legacy-parity.md`
- Assets: migrate `/uploads/fatture/` in Phase 2 with e-invoicing
.larapilot/research/legacy-parity.md (excerpt)
| Legacy module | Current impl | New impl | Migration | Status |
| --- | --- | --- | --- | --- |
| Anagrafica clienti | PHP forms + `clienti` table | Filament CustomerResource | ETL `clienti` → `customers` | preserve |
| Ordini vendita | Custom MVC + 8 status codes | Order model + Filament | Map statuses 1:1 | preserve |
| Magazzino | Stock tables + manual adjustments | — | Phase 2 | defer |
| Fatturazione SDI | XML + PDF generation | — | Phase 2 + assets port | defer |

On /larapilot-spec, Tom bootstraps migration specs first.

specs/US-001.yaml (excerpt — first story)
code: US-001
title: "Legacy schema analysis and ETL plan"
priority: HIGH
status: TODO
body: |
  #### US-001: Legacy schema analysis and ETL plan

  **Epic:** EP-001 Legacy migration | **Priority:** HIGH | **Points:** 5
  **Type:** Migration | **Traces to:** Legacy parity (anagrafica + ordini)

  **User Story**
  As a developer,
  I want a documented ETL plan from legacy MySQL to Laravel,
  so that customer and order data migrates without loss.

  **Acceptance Criteria**
  - [ ] Field-level mapping `clienti` → `customers`, `ordini` → `orders`
  - [ ] Idempotent import command with dry-run
  - [ ] Parity matrix rows updated with test evidence
  - [ ] Sabrine signs off before US-002 (CustomerResource UI)
You do
Drop legacy snapshot in .larapilot/legacy/; invoke inception; answer origin + scope rounds
Agent does
Writes PRD + legacy-parity.md; /larapilot-spec with migration specs first
Next step
/larapilot-plan US-001 → implement → /larapilot-review (Sabrine checks parity)

New feature

/larapilot-feature

Invoicing SaaS shipped — stakeholder wants PDF export on invoice detail.

Your editor
/larapilot-feature "Add PDF export for invoices"

Mark and Tom read the PRD and the backlog, confirm nothing already covers it, then ask three short rounds. Nothing is saved until you confirm the readback.

💎 Mark: Got it — PDF export for invoices from the detail page. Let's align scope and priority.

AskQuestion — Round 1 (scope & priority)
  1. MoSCoW: Should · Must · Could
  2. Traces to: New FR-011 · Extends FR-004 (Invoicing) · Changes FR-004 · Standalone enhancement
  3. Journey and persona: J-002 Invoice a client (Freelancer) · New journey · None

You pick: Should, New FR-011, J-002.

👾 Andrew: Prior art for the feature: spatie/laravel-pdf renders a Blade view to PDF and is maintained. No reason to build a renderer.

AskQuestion — Round 2 (delivery shape)
  1. Complexity: Small (1 spec) · Medium · Large (split epics)
  2. Mockup first? Yes — /larapilot-design · No — plan directly · Already have mockups
  3. Legacy touch? No · Maps to legacy parity · Needs scraping/porting

You pick: Small, No — plan directly, No.

AskQuestion — Round 3 (backlog placement)
  1. Priority: HIGH · MEDIUM · LOW · CRITICAL
  2. Epic: EP-002 Invoicing · New epic · Other
  3. Blocked by: None · US-004 (Invoice detail UI)

You pick: MEDIUM (Should), EP-002 Invoicing, US-004.

🔎 Tom: Acceptance criteria, from what FR-011 will promise:

  • The PDF downloads from the invoice detail page
  • 403 when the user cannot view that invoice
  • The PDF carries line items, tax breakdown, and the tenant logo
  • NFR-001 applies: the download starts within 300 ms at p95

💎 Mark: Readback — new FR-011 Export an invoice as PDF, Should, journey J-002 · four criteria · the PRD gains FR-011 and one history row · story in EP-002, MEDIUM, after US-004.

AskQuestion — readback
  1. Add to backlog · Revise

You pick: Add to backlog.

Mark adds FR-011 to the PRD with a row in its revision history, then the story US-011 is created.

specs/US-011.yaml (excerpt)
code: US-011
title: "Export invoice as PDF"
priority: MEDIUM
status: TODO
body: |
  #### US-011: Export invoice as PDF

  **Epic:** EP-002 | **Priority:** MEDIUM | **Points:** 3 | **Status:** TODO
  **Blocked by:** US-004
  **Type:** Feature
  **Traces to:** J-002 · FR-011 (MoSCoW: Should) · NFR-001
  **Prior art:** package spatie/laravel-pdf — maintained, renders Blade

  **User Story**
  As a freelancer,
  I want to download a PDF of an invoice from its detail page,
  so that I can archive or send it to clients offline.

  **Acceptance Criteria**
  - [ ] PDF download from invoice detail for authorized users
  - [ ] 403 when the user cannot view the invoice
  - [ ] PDF contains line items, tax breakdown, and tenant logo
  - [ ] Download starts within 300 ms at p95 (NFR-001)

The feature changes something that already works

That is a change request: the product does what was agreed and you now want it different. The requirement is edited in place — same id, new behavior — and the revision history records before and after. The story that shipped the old behavior stays DONE; the new story cites it as Supersedes. Rules: How the PRD changes.

You do
Describe the feature in one line; answer three rounds; confirm the readback
Agent does
Checks for packages that already do it, writes the criteria, adds FR-011 to the PRD, saves story US-011
Next step
/larapilot-plan US-011

Bug resolution

/larapilot-bug

SSO works in Chrome, fails in Safari — maps to done story US-003.

Your editor
/larapilot-bug "SSO login fails on Safari"

Sophia triages with AskQuestion and logs to support/intake.md. Before the first question she reads the logs of the application — every time, whatever the report says: a defect told in words is often already written there, with its exception, its file, and its line. larapilot:logs gives them as entries with the repeats counted and the secrets redacted, so nothing private lands in chat or intake.

🎧 Sophia: Report received — SSO works on Chrome, fails on Safari. Reading the logs first.

Agent — the logs, every time
php artisan larapilot:logs --group --level=warning --since=7d
php artisan larapilot:logs --search="sso"

🎧 Sophia: One thing stands out: InvalidStateException at app/Http/Controllers/SsoController.php:41, 38 times in three days, the last an hour ago. That goes into intake and into the fix spec. Now let's classify and route.

AskQuestion — Round 1 (severity & environment)
  1. Severity: Critical · High · Medium · Low
  2. Environment: Production · Staging · Local · Unknown
  3. Security-related? Yes · No · Unsure

You pick: High, Production, Unsure (auth boundary — Lars will review).

🎧 Sophia: Production auth failure — the logs name the place; the health of the runtime rules out the infrastructure.

Agent — the health of the runtime
php artisan larapilot:diagnostics --no-logs

🎧 Sophia: Checks look healthy (database / storage_writable ok), so it is the code: the OAuth state does not survive the redirect on Safari.

AskQuestion — Round 2 (reproduction)
  1. Reproducible? Always · Sometimes · Once · Not yet tried
  2. Affected area: Auth / SSO · Billing · Other · Unknown
  3. Maps to existing spec? Yes — US-003 · No — new fix spec · Unsure

You pick: Always (Safari 17+), Auth / SSO, Yes — US-003.

AskQuestion — Round 3 (routing)
  1. Preferred path: Rework existing spec · New fix spec · Log only
  2. Urgency: Hotfix now · Next sprint · Backlog

You pick: New fix spec (US-003 is DONE and stays closed), Next sprint (High but not Critical — no hotfix/* branch).

🔎 Tom: Promise broken: nothing written — FR-003 promises SSO login and names no browser. That is a requirement gap, and the fix needs a sentence to be measured against.

php artisan larapilot:logs --search="sso" (excerpt)
{
  "file": { "name": "laravel.log", "size": "4.2 MB", "modified": "2026-07-15 09:11:40" },
  "redacted": true,
  "holds": {
    "levels": { "error": 41, "warning": 12, "info": 2380 },
    "from": "2026-07-01 00:00:04", "to": "2026-07-15 09:11:40", "whole_file": true
  },
  "filters": { "search": "sso" },
  "entries": [
    {
      "time": "2026-07-15 08:14:02", "level": "error", "env": "production",
      "message": "Invalid state: the OAuth state does not match",
      "class": "Laravel\\Socialite\\Two\\InvalidStateException",
      "where": "app/Http/Controllers/SsoController.php:41",
      "frames": [
        "app/Http/Controllers/SsoController.php:41 Laravel\\Socialite\\Two\\AbstractProvider->user()"
      ]
    }
  ]
}

The command answers with what the file holds, then the entries: when, what, and where — a path of the server read as a file of the repository — with the frames of the application and not the sixty of the framework under them. The logs are the ones of the machine the command runs on: env says which environment wrote them, and nothing logged is an answer too. Full reference: Diagnostics and logs.

Sophia appends a normalized entry to the support log, with what the logs said:

.larapilot/docs/support/intake.md (excerpt)
## BUG-20260715-sso-safari

- **Reported:** 2026-07-15
- **Severity:** High
- **Environment:** Production
- **Summary:** SSO login fails on Safari; works on Chrome
- **Logs:** InvalidStateException at app/Http/Controllers/SsoController.php:41 ×38, last 2026-07-15 08:14:02 (production)
- **Steps to reproduce:**
  1. Open app in Safari 17+
  2. Click "Sign in with Google"
  3. Complete OAuth — redirect returns to /login with error
- **Expected / Actual:** Expected dashboard · Actual login error
- **Promise broken:** none — requirement gap (FR-003 names no browser)
- **Affected spec:** US-003 (DONE)
- **Routed to:** spec-add US-015, related US-003
- **Security:** unsure — Lars/Oliver tagged
- **Occurrences:** 2026-07-15 Production

US-003 shipped and stays closed: the skill creates fix spec US-015 and relates it to the story that shipped the behavior. Because nothing ever named a browser, the PRD gains the sentence the fix is measured against — a row in the compatibility NFR and a done-means bullet under FR-003, never an FR-012: Fix Safari.

specs/US-015.yaml — body (excerpt)
#### US-015: Fix — SSO login fails on Safari

**Type:** Fix · **Severity:** High · **Security:** Lars/Oliver — unsure
**Related:** US-003
**Traces to:** J-001 · FR-003 · NFR-007
**Promise broken:** none written — FR-003 now reads "SSO login succeeds on Safari 17+, macOS and iOS"

**Steps to Reproduce**
1. Safari 17+ → Sign in with Google → redirect returns to /login with an error

**Acceptance Criteria**
- [ ] SSO login succeeds on Safari 17+ (macOS and iOS)
- [ ] SameSite / session cookie behaviour verified for the OAuth callback
- [ ] Regression test covers the Safari user-agent and fails without the fix (Anne)
- [ ] Root cause named in the fix commit; auth developer doc updated
- [ ] Lars/Oliver review if the cookie or state token issue is confirmed

Which route, by the status of the story

The defect maps to a story in Route
REVIEW spec-request-changes US-XXX — back to TODO with the feedback. It is the only status that command accepts
TODO · PLANNED The story is re-issued through spec-add with the criteria added — same code, status kept. A planned one is planned again
IN PROGRESS You are told work is under way; on your consent the criteria join the story and the next task
DONE New fix spec, Type: Fix, related to the story. A done story is never reopened
No story at all New fix spec. Critical in production → Jack's hotfix/* note
You do
Describe the defect; answer the triage wizard
Agent does
Reads the logs every time (larapilot:logs, secrets redacted); cites what they say in support/intake.md; routes rework or fix spec
Next step
/larapilot-plan US-015

Bug or feature?

/larapilot-triage → /larapilot-bug or /larapilot-feature

Invoicing SaaS shipped. A client email lands: they want the company logo on the invoice PDF. It reads like a wish — is it one?

Your editor
/larapilot-triage "We would like the company logo on the invoice PDF"

Triage is the front door. It does not interview you: it measures the request against what the product promised — the backlog and the PRD — and says which skill owns it. Wording is a hint, never the verdict.

The promise test

Verdict When Evidence cited Runs
Bug An FR or an acceptance criterion promises it, or it worked before FR-XXX done-means, US-XXX criterion, or the release where it worked /larapilot-bug
Bug — requirement gap Nothing written, but an FR covers the area and any user would take the behavior for granted (browser, locale, data kept, permission respected) Parent FR-XXX /larapilot-bug
Feature No FR covers it — a new capability, or an extension of one Nearest FR-XXX, or none /larapilot-feature
Feature — change request It works as specified and you now want it different The FR or criterion that describes today's behavior /larapilot-feature

Tom and Mark look the request up: spec-list titles, the outline prd-show gives of the PRD, then at most one spec and one FR. The whole PRD is never loaded.

Agent — evidence
php artisan larapilot:context triage                 # settings, paths, the core files only
php artisan larapilot:spec-list                      # the backlog without the bodies
php artisan larapilot:prd-show                       # outline: every FR with its title
php artisan larapilot:spec-show US-011 --fields=id   # body + criteria, tasks reduced to ids

🎧 Sophia: Bug — US-011 criterion "tenant logo in PDF" not met → larapilot-bug

One line, with its evidence. US-011 is DONE and one of its criteria reads “PDF contains line items, tax breakdown, and tenant logo”: the request was phrased as a wish, but the product already promised it. Triage then runs /larapilot-bug in the same turn — you do not type a second command — and passes what it found:

Triage handoff (carried into the target skill)
Triage handoff
request: We would like the company logo on the invoice PDF
verdict: Bug
evidence: US-011 criterion "PDF contains line items, tax breakdown, and tenant logo"
maps to: US-011 (DONE)
settled by: evidence

Sophia's bug interview starts with Maps to existing spec? already answered, and does not ask you to describe the problem again. From here it is the bug walkthrough: severity, environment, intake log, and a fix story related to US-011 — which is DONE and stays closed.

Same product, three other requests

Request Evidence Verdict
“Invoice export to CSV does not work” No FR and no criterion mentions CSV — FR-011 is PDF only Feature — reported as broken, never promised. /larapilot-feature starts with Traceability answered
“The PDF should open in the browser instead of downloading” US-011 promises a download, and delivers it Feature — change request. /larapilot-feature edits FR-011 instead of adding an FR
“Login fails on Safari” FR-003 promises SSO login; no browser list anywhere Bug — requirement gap. /larapilot-bug clarifies FR-003 — see How the PRD changes

When the evidence does not settle it

One question, never a round of them. Severity, MoSCoW, priority, epic, and release are asked later, by the skill that owns them.

🎧 Sophia: “Reminder emails arrive late” — FR-007 promises reminders, no timing anywhere. Your call.

AskQuestion — verdict (skippable)
  1. Bug — it should already work
  2. Feature — it is new, or a change to what was agreed

You pick: Bug — it should already work. Skipped instead? An FR covers the area → Bug — requirement gap; no FR covers it → Feature.

Your answer goes to the decision journal — it is the record of who decided that a report was a defect and not new scope:

Agent — only when you settled the verdict
php artisan larapilot:decision-log --topic="Triage: reminder-emails-late" \
  --value="Bug — requirement gap" --source=askquestion --skill=larapilot-triage

Several requests in one message

“Login is broken on Safari, and we'd like CSV export too.” Sophia lists the two, classifies each, and hands off one at a time — bugs before features, the most severe bug first — coming back to the next when the target skill reaches its next steps.

Neither a bug nor a feature

No handoff: triage says where the request belongs in one line and stops.

Request Exit
No PRD and no backlog /larapilot-adopt when the code exists, otherwise /larapilot-inception
Redefines the product vision or the target user /larapilot-inception
Changes the PRD: priority, scope, a decision /larapilot-prd
An open spec already covers it, and nothing is broken Names US-XXX and its status. No new spec
Several stories for the backlog /larapilot-spec
A question, a how-to, or a project setting Answers it, or /larapilot-settings. No spec

Called the wrong skill directly?

/larapilot-bug and /larapilot-feature run the same check on their own evidence. A “bug” nothing ever promised is handed to /larapilot-feature; a “feature” an FR already promises is handed to /larapilot-bug. Once, on new evidence only, and never against a verdict you settled.

What it costs

Triage reads the every-skill runtime rows and nothing else — not runtime-ops, not runtime-discovery — so the branch it does not take costs nothing. The target skill loads its own packs, does not re-read what triage loaded, and closes the session with one Zoey line and one Lucille ledger entry that cover both.

You do
Paste the request as it arrived; answer one question only when the evidence is not enough
Agent does
Cites the FR or criterion, states the verdict in one line, runs the right skill with the handoff. Writes no spec, PRD edit, or intake entry itself
Next step
None to type — you are already in /larapilot-bug or /larapilot-feature

PRD revision

/larapilot-prd

Invoicing SaaS, PRD written three weeks ago, fourteen stories in the backlog. Nothing is broken and nothing new is wanted — but the client dropped the CSV export, wants 2FA mandatory for admins, and nobody ever said what "fast" means.

Your editor
/larapilot-prd "Drop the CSV export, make 2FA mandatory for admins, and say what 'fast' means"

This is neither a bug nor a feature: the product will promise fewer things, in a different order, and one of them more precisely. That is a revision. The skill does four things, in this order, and writes nothing before you confirm.

1 · Name what kind of change it is

💎 Mark: Re-scope — FR-009 Should → Won't, FR-006 Could → Must · Sharpen — NFR-001. Dropping the export frees US-014; accountants keep the integration in FR-011.

🔎 Tom: FR-006 has to say what mandatory means. Two done-means: an admin without 2FA cannot reach the panel; recovery codes are shown once.

📐 John: "Fast" — p95 under 300 ms on list pages at 10k rows per tenant, verified by a k6 run in CI. Is that the number, or do you have one from the client?

You: That number is fine.

The team asks only what it cannot work out. When you do not have an answer, it is written as an open question with an owner — never as an invented number. The seven kinds of revision are listed in The PRD → Revision kinds.

2 · Find the stories that rest on it

Agent — php artisan larapilot:prd-impact --ids=FR-009,FR-006,NFR-001
{
  "schema": "larapilot/v1",
  "kind": "prd_impact",
  "data": {
    "scope": "ids",
    "specs": [
      { "code": "US-003", "status": "DONE",    "matches": ["FR-006"],  "action": "new_spec" },
      { "code": "US-009", "status": "PLANNED", "matches": ["NFR-001"], "action": "update_and_replan" },
      { "code": "US-014", "status": "TODO",    "matches": ["FR-009"],  "action": "update_spec" }
    ],
    "untraced": [],
    "unknown": [],
    "summary": { "ids": 3, "specs": 3, "by_action": { "new_spec": 1, "update_and_replan": 1, "update_spec": 1 } }
  }
}

Three stories cite the ids being changed. What happens to each depends on how far it has gone — a story in TODO is simply rewritten, a DONE one is never reopened. The full table is in The PRD → The backlog follows.

3 · Read the change back

Mark, in chat — then AskQuestion: Apply · Revise · Cancel
FR-009 Export CSV      Should → Won't (Retired 2026-08-02 — replaced by the accountant integration)
FR-006 2FA for admins  Could → Must · done-means +2
NFR-001 Performance    "fast" → p95 < 300 ms at 10k rows, verified by k6 in CI
Backlog                US-014 TODO → delete · US-009 PLANNED → update and replan · US-003 DONE → change request

You pick: Apply.

4 · Write the PRD and align the backlog

PRD.md after the revision (excerpt)
### FR-009: Export invoices as CSV

**MoSCoW:** Won't · **Journey:** J-003 · **Persona:** Accountant
**Retired:** 2026-08-02 — replaced by the accountant integration (FR-011)

## PRD Revision History

| Date | Trigger | Summary |
| --- | --- | --- |
| 2026-07-01 | larapilot-inception | Initial PRD |
| 2026-08-02 | larapilot-prd — Re-scope + Sharpen | FR-009 retired; FR-006 Could → Must (+2 done-means); NFR-001 p95 < 300 ms |

FR-009 is retired, not deleted: its heading stays, so every story, commit, and document that cites FR-009 still points somewhere. Then, with your consent, US-014 is deleted, US-009 is rewritten with the new target, and US-003 stays closed.

Two other ways to use it

Situation Type What happens
You edited PRD.md by hand /larapilot-prd "I edited the PRD by hand" It reads the git diff of the file, names the kind of change, and adds what a hand edit skips: the history row, validation, the dashboard snapshot, the backlog check
The PRD was written by an older Larapilot and validate-prd shows warnings /larapilot-prd "Upgrade the PRD" It builds the missing sections from what exists — journeys from the stories, the domain model from the requirements and the code — and marks each item derived — confirm. What nobody knows becomes an open question
You do
Say what should change; answer only what the team cannot work out; confirm the readback
Agent does
Names the kind, finds the stories affected, edits the PRD with a history row, validates, aligns the stories by status
Next step
/larapilot-plan US-009 · /larapilot-feature "Enforce 2FA on existing admin accounts"

External frontend repo

/larapilot-inception → /larapilot-spec → /larapilot-implement

Booking SaaS: Laravel API + an Angular app inside the company's Nx monorepo, next to the apps of other products. Laravel is the only Larapilot cockpit — the monorepo is a linked write target with its own rules. Reference: External frontend repo.

1 · Inception (Laravel)

Laravel editor
/larapilot-inception "Clinic booking API — React SPA in a separate repo"

John + Joe ask Frontend Topology → you pick API + external frontend, Angular, and provide the absolute path when asked (stored in LARAPILOT_FRONTEND_REPO_PATH). Filament for ops admin on Laravel. PRD records stack, projects, API + OpenAPI as Must — not the machine path.

2 · Link + scan (Laravel)

Laravel terminal
php artisan larapilot:frontend-set --path=/absolute/path/to/acme-frontend
php artisan larapilot:frontend-scan
php artisan larapilot:frontend-set --project=clinic --project=clinic-admin

frontend-set writes LARAPILOT_FRONTEND_REPO_PATH in .env. frontend-scan sees an Nx workspace with 40 projects and asks which are this product's; you name clinic and clinic-admin. The next scan knows Angular 19, pnpm, the libraries the clinic apps own and the ones other teams share, the team's AGENTS.md and Cursor rules, that components are standalone with signal inputs and @if, and that the API client is generated with orval.

3 · Deliver (Laravel only)

Laravel editor
/larapilot-spec
/larapilot-plan US-003
/larapilot-implement US-003

API stories touch Laravel. UI stories use plan tasks with repo: frontend and project: clinic — implement reads the team's rules first, asks frontend-rules which ones govern each file, regenerates the orval client after the API task, writes under apps/clinic/… the way the code already is, runs nx run clinic:test and nx affected, and commits with the repository's hooks on. With the frontend team building itself, frontend-set --mode=handoff turns implement into the backend tasks plus frontend-brief US-003. Mockups stay in Laravel .larapilot/mockups/.

You do
Work only in the Laravel workspace — set path, spec, plan, implement
Agent does
Scans FE · follows its rules · plans BE + FE tasks · writes both repos from Laravel
Next step
/larapilot-spec → implement Must stories

Tracker sync

/larapilot-tracker

Invoicing SaaS, already in delivery: 12 specs in the backlog, four of them planned. The client's PM runs everything from Linear and will never open backlog.yaml. You mirror the backlog into their team so they can follow progress, and read status back before standup. Full reference: Project trackers.

1 · Point Larapilot at the tracker

Editor
/larapilot-tracker

🔗 Matt: No provider configured yet — 12 specs, 0 linked. Let's pick the destination first.

AskQuestion — Project tracker
  1. Where should the backlog be mirrored: Linear · Jira · Asana · ClickUp · Trello · Monday

You pick: Linear — issues on a team, plan tasks as sub-issues.

🔐 Lars: I need a personal API key (Linear → Settings → API) and the team key — the short prefix on your issues, e.g. ENG. The key goes in .env, never in .larapilot/: that directory is committed.

You paste: the key and ENG.

.env
LARAPILOT_TRACKER_ENABLED=true
LARAPILOT_TRACKER_PROVIDER=linear
LARAPILOT_LINEAR_API_KEY=lin_api_xxxxxxxxxxxxxxxxxxxx
LARAPILOT_LINEAR_TEAM=ENG
Terminal — verify before touching anything
php artisan larapilot:tracker-status --ping
Envelope (excerpt)
{
  "kind": "tracker-status",
  "data": {
    "provider": "linear",
    "ready": true,
    "missing_config": [],
    "sync_tasks": true,
    "status_map": { "TODO": "Todo", "PLANNED": "Todo", "IN PROGRESS": "In Progress",
                    "REVIEW": "In Review", "DONE": "Done" },
    "specs": { "total": 12, "linked": 0, "unlinked": ["US-001", "US-002", "…"] },
    "connection": { "ok": true, "detail": "Connected to Linear team ENG.", "target": "Engineering" }
  }
}

2 · Dry-run first — and hit the status map

On a backlog that has never been synced, always dry-run. It reports what would change without calling the provider at all.

Terminal
php artisan larapilot:tracker-push --dry-run
Envelope (excerpt)
"dry_run": true,
"stories": [ { "code": "US-001", "action": "created", "ref": null,
               "tasks": { "created": 4, "updated": 0, "removed": 0, "unchanged": 0 } }, … ],
"summary": { "created": 12, "updated": 0, "unchanged": 0, "tasks_created": 17, … }

The real push then fails — and this is the common first-run outcome, not an edge case:

Terminal
php artisan larapilot:tracker-push   # exit code 3
Envelope (excerpt)
{
  "kind": "error",
  "error": {
    "code": "E_CONNECTOR",
    "message": "12 of 12 stories failed to sync.",
    "details": {
      "errors": [{
        "code": "US-001",
        "message": "Linear team ENG has no workflow state named \"Todo\". Existing states: Backlog, In Progress, In Review, Done. Rename the state or adjust larapilot.tracker.providers.linear.status_map."
      }, … ]
    }
  }
}

🔗 Matt: The client's team calls its first column Backlog, not Todo. Larapilot will not create a column in someone else's tracker — two ways out: rename the state in Linear, or point the map at what already exists. I'd take the second: it's their board.

config/larapilot.php
'linear' => [
    'api_key' => env('LARAPILOT_LINEAR_API_KEY'),
    'team' => env('LARAPILOT_LINEAR_TEAM'),
    'status_map' => [
        'TODO' => 'Backlog',        // was 'Todo'
        'PLANNED' => 'Backlog',     // same column — not drift
        'IN PROGRESS' => 'In Progress',
        'REVIEW' => 'In Review',
        'DONE' => 'Done',
    ],
],

3 · Push

Terminal
php artisan larapilot:tracker-push
Envelope (excerpt)
"stories": [
  { "code": "US-001", "action": "created", "ref": "ENG-42",
    "url": "https://linear.app/acme/issue/ENG-42",
    "tasks": { "created": 4, "updated": 0, "removed": 0, "unchanged": 0 } }, … ],
"summary": { "created": 12, "updated": 0, "unchanged": 0, "tasks_created": 17, … },
"errors": [],
"link_file": ".larapilot/tracker.yaml"

In Linear the client now sees ENG-42 — US-001 — Staff can issue an invoice, sitting in Backlog, carrying the spec body, priority, points as the estimate, and the epic — with four sub-issues, one per plan task. Larapilot writes the mapping to disk:

.larapilot/tracker.yaml — commit this
providers:
  linear:
    US-001:
      id: 9f2c1a4e-7b30-4d1e-9c55-2a6f81b0c3d7
      key: ENG-42
      url: https://linear.app/acme/issue/ENG-42
      fingerprint: 6b1f0c2d…
      pushed_at: '2026-07-27T11:04:12+00:00'
      tasks:
        TASK-01:
          id: 3ad5b9c1-…
          url: https://linear.app/acme/issue/ENG-43
          fingerprint: 8c02e5a7…
provider: linear
updated_at: '2026-07-27T11:04:12+00:00'
Commit tracker.yaml. It is how the team shares one mapping — without it, your colleague's first push creates a second ENG-… for every story. It holds identifiers only, never the API key.

4 · Normal delivery — the push is cheap

You keep working as usual: /larapilot-plan, /larapilot-implement, /larapilot-review. Re-push whenever you like — unchanged stories are fingerprinted and skipped without an API call.

Terminal — after planning US-005 and finishing two tasks on US-003
php artisan larapilot:tracker-push
Envelope (excerpt)
"summary": { "created": 0, "updated": 2, "unchanged": 10,
             "tasks_created": 5, "tasks_updated": 2, "tasks_removed": 1 }

The removed subtask is a plan task that disappeared when you re-planned US-005 — the matching sub-issue is deleted rather than left orphaned in the client's board.

5 · Before standup — read the drift

Overnight the PM dragged two cards. tracker-pull with no flags changes nothing — it reports.

Terminal
php artisan larapilot:tracker-pull
Envelope (excerpt)
"apply": false,
"stories": [
  { "code": "US-003", "ref": "ENG-44", "local_status": "IN PROGRESS",
    "remote_status": "In Review", "suggested_status": "REVIEW",
    "drift": true, "applied": false, "blocked": null },
  { "code": "US-007", "ref": "ENG-48", "local_status": "REVIEW",
    "remote_status": "Done", "suggested_status": "DONE",
    "drift": true, "applied": false,
    "blocked": "Remote status maps to DONE. Approve through /larapilot-review or larapilot:spec-approve so the merge commit is recorded." },
  { "code": "US-002", "ref": "ENG-43", "local_status": "PLANNED",
    "remote_status": "Backlog", "drift": false, "applied": false } ],
"summary": { "checked": 12, "in_sync": 10, "drifted": 2, "applied": 0, "missing": 0 },
"hint": "2 stories drifted. Re-run with --apply to write the mapped statuses into the backlog."

🔗 Matt: Two drifts. US-003 genuinely moved to review — safe to apply. US-007 the PM marked Done, which Larapilot will not accept from a tracker.

💎 Mark: Note US-002: local PLANNED, remote Backlog. Both TODO and PLANNED map to that column, so it is in sync, not drift — you won't get a false alarm every morning.

Terminal
php artisan larapilot:tracker-pull --apply
Envelope (excerpt)
"summary": { "checked": 12, "in_sync": 10, "drifted": 2, "applied": 1, … },
"hint": "Statuses that map to DONE were left alone — approve them through /larapilot-review."

US-003 is now REVIEW in backlog.yaml. US-007 is untouched — you finish it the normal way:

Editor
/larapilot-review US-007

What pull will never do

Change made in Linear Effect on .larapilot/ Why
Card moved to In Review Applied with --apply Status is the one thing the tracker legitimately observes
Card moved to Done Reported, never applied DONE is a human gate that records the merge commit — spec-approve owns it
Issue title or description edited Ignored, overwritten on the next push Spec text is owned by .larapilot/; the card footer says so
Card moved to a state outside the map Drift with no suggestion Better a visible unknown than a guessed status
Issue deleted Flagged missing; next push recreates it The link is stale, not the spec
Comment added Imported once as non-blocking internal feedback Opt-in via LARAPILOT_TRACKER_PULL_COMMENTS=true

6 · Keep it fresh (optional)

.github/workflows/tracker.yml (excerpt)
- name: Sync backlog to Linear
  run: php artisan larapilot:tracker-push
  env:
    LARAPILOT_TRACKER_ENABLED: true
    LARAPILOT_TRACKER_PROVIDER: linear
    LARAPILOT_LINEAR_API_KEY: ${{ secrets.LARAPILOT_LINEAR_API_KEY }}
    LARAPILOT_LINEAR_TEAM: ENG

Run it on the default branch after merges. Because unchanged stories cost no API call, a push on every merge is cheap.

The tracker is a window, not a second workflow. Scope still changes through /larapilot-feature and /larapilot-prd, plans through /larapilot-plan, and acceptance through /larapilot-review. What the client gets is visibility in the tool they already use — not a second place to run delivery from.

Your own skill

/larapilot-custom-skill → /acme-predeploy-gate

An agency runs a dozen Laravel apps. Before every production deploy the lead goes through the same checks by hand: nothing half-done in the backlog, install healthy, Pint and Larastan green, security scan clean — and every few weeks one gets skipped. You turn that checklist into a slash command the whole team runs the same way, built on Larapilot's own CLI. Reference: Custom skills.

1 · Describe the ritual

Your editor
/larapilot-custom-skill "A pre-deploy gate we run before every production release: nothing left in review, doctor healthy, quality green, security scan when it's on, then GO or NO-GO"

Zoey interviews, Albert tightens the wording, Sarah persists. Before asking anything the skill reads the project and checks for name clashes:

Agent
php artisan larapilot:context custom-skill   # settings, paths, the files this skill reads
php artisan larapilot:custom-skill-list      # existing custom skills — avoid duplicate names

🤖 Zoey: No custom skills in .larapilot/skills/ yet. Three short rounds, then I show you the draft before anything is saved.

AskQuestion — Round 1 (intent)
  1. What are we building: One new custom skill · Several related skills (shared prefix) · List existing custom skills only

You pick: One new custom skill.

AskQuestion — Round 2 (trigger)
  1. Slash name: acme-predeploy-gate · predeploy-gate · Other
  2. When should the agent reach for it? — becomes the YAML description Boost routes on

You pick: acme-predeploy-gate, and describe it: "Before a production release. Use when someone says deploy, go live, release to production, or asks whether main is safe to ship."

🤖 Zoey: Good trigger — it names the words people actually type. The acme- prefix keeps it clear of the packaged larapilot-* names.

AskQuestion — Round 3 (workflow)
  1. Personas: Jack + Lars + Anne · Jack only · Other
  2. Runtime packs: Every-skill core only · + runtime-ship.md · Other
  3. CLI commands, in order: list them in chat after this round

You pick: Jack + Lars + Anne, Every-skill core only, and list the checks: backlog, doctor, quality, checkpoint when the scan is on, a scope line, notify.

📝 Albert: Numbered the steps, one line per check, and a hard stop on the first NO-GO — a gate that keeps going after a failure gets ignored.

2 · Review the draft

Zoey shows the draft in chat. It has the same sections as a packaged skill — front matter, Shared Runtime, Team, Config & CLI, Workflow, Output Economy — so every agent reads it the same way.

.larapilot/tmp-acme-predeploy-gate-SKILL.md (draft)
---
name: acme-predeploy-gate
description: "Pre-deploy gate before a production release — GO or NO-GO. Use when someone says deploy, go live, release to production, or asks whether main is safe to ship."
---

# Acme — Pre-deploy gate

Run before every production deploy. Read-only: it changes no workflow state and writes no code.

## Context

`php artisan larapilot:context deploy-gate` — with `--session={token}` when this conversation already holds one. Read every file under `data.runtime.read`.

## The Team

| Agent | Role |
| --- | --- |
| 🚀 **Jack** | Runs the gate, owns the verdict |
| 🔐 **Lars** | Security scan and its waivers |
| 🧪 **Anne** | Test evidence for what is about to ship |

## Config & CLI

1. `data.settings` comes from the `context` envelope.
2. This skill uses: `spec-list`, `doctor`, `quality`, `metrics`, `decision-log`, `notify`.

## Workflow

1. **Nothing half-done ships** — `php artisan larapilot:spec-list --status=REVIEW`, then
   `--status="IN PROGRESS"`. Any spec listed → **NO-GO**; name it.
2. **Install healthy** — `php artisan larapilot:doctor`. `data.healthy` false → **NO-GO** with the failing checks.
3. **Quality green** — `php artisan larapilot:quality`. An `E_QUALITY` error → **NO-GO** with the findings.
4. **Security** — only when `data.settings.security_scan` is `YES`: `php artisan checkpoint:scan`.
   A `FAIL` → **NO-GO**, unless the user grants a waiver; log it with
   `php artisan larapilot:decision-log --topic="deploy waiver" --value="…" --source=chat --skill=acme-predeploy-gate`.
5. **Scope** — `php artisan larapilot:metrics` → one line: stories DONE and still open.
6. **Verdict** — `GO` or `NO-GO`, then the reasons. When `data.settings.notifications` is `YES`:
   `php artisan larapilot:notify --event=custom --title="Pre-deploy: GO"`.

Stop at the first NO-GO. Never deploy, push, tag, or merge from this skill.

## Output Economy

**Low** — one line per check (`✓ doctor` / `✗ quality — 2 Larastan errors`), then the verdict.

3 · Save and register — one command

Sarah never writes the canonical file by hand. She saves the approved draft to a temp file and hands it to Artisan:

Agent
php artisan larapilot:custom-skill-add --name=acme-predeploy-gate --file=.larapilot/tmp-acme-predeploy-gate-SKILL.md
stdout — larapilot/v1 envelope
{
  "schema": "larapilot/v1",
  "kind": "custom_skill",
  "data": {
    "skill": {
      "name": "acme-predeploy-gate",
      "path": "…/.larapilot/skills/acme-predeploy-gate/SKILL.md",
      "relative_path": ".larapilot/skills/acme-predeploy-gate/SKILL.md",
      "registered": [".ai/skills/acme-predeploy-gate", ".claude/skills/acme-predeploy-gate"]
    },
    "boost_published": true
  }
}

That one call validates the skill (kebab-case name, non-empty description), writes the canonical copy under .larapilot/skills/, mirrors the folder into .ai/skills/ — Laravel Boost's custom-skill source — and into every agent skill folder that already exists (.claude/skills/ here, because the project uses Claude Code), then runs boost:update. The slash command is live; nobody has to run anything else. Sarah deletes the temp file.

.larapilot/skills/ ├── .gitkeep └── acme-predeploy-gate/ └── SKILL.md # canonical copy — commit this .ai/skills/acme-predeploy-gate/SKILL.md # Boost source (mirror) .claude/skills/acme-predeploy-gate/SKILL.md # agent folder (mirror — only because it existed)

4 · Run it

Your editor — before the deploy
/acme-predeploy-gate

🚀 Jack: Pre-deploy gate — acme-shop.

first run
✓ backlog   — nothing in REVIEW or IN PROGRESS
✓ doctor    — healthy
✗ quality   — Larastan: 2 errors in app/Policies/InvoicePolicy.php
NO-GO — fix the two Larastan errors, then run /acme-predeploy-gate again.

After the fix:

second run
✓ backlog · ✓ doctor · ✓ quality · ✓ checkpoint (0 FAIL, 2 WARN) · 14 stories DONE, 3 open
GO — Slack notified.

5 · Share it with the team

Commit .larapilot/skills/. On a teammate's machine, after git pull:

Teammate's terminal
php artisan larapilot:custom-skill-list   # registers every skill found on disk

custom-skill-list — also run by larapilot:update and by opening the dashboard Skills page — mirrors each skill into .ai/skills/ and the agent folders that exist. It does not call boost:update; run that once if your editor's skill folder was just created. /larapilot/skills lists the skill with its trigger, its description, the file path, and a Registered chip.

6 · Change it, grow it, retire it

You want to Do
Change the steps Run /larapilot-custom-skill again and ask to overwrite, or edit a copy and re-save it with larapilot:custom-skill-add --name=acme-predeploy-gate --file=… --force. Without --force the command refuses to overwrite.
Add related skills Round 1 → Several related skills: one custom-skill-add per skill under a shared prefix — acme-predeploy-gate, acme-hotfix-gate, acme-release-notes.
Remove it Delete .larapilot/skills/acme-predeploy-gate/ and its mirrors in .ai/skills/ and the agent folders, then run php artisan boost:update. Registration only adds or refreshes copies; it never deletes one.
You do
Describe the ritual, answer three rounds, approve the draft
Agent does
Drafts SKILL.md in the packaged-skill shape, saves it with custom-skill-add, registers it with Boost
Next step
Commit .larapilot/skills/; run /acme-predeploy-gate before every deploy

Reference

Every skill, setting, file, and command, one chapter each. Read Concepts first if a word is new, and use the search field at the top of the page to jump to a command or a setting by name.

How it works

Three layers: skills orchestrate the conversation, artifacts in git are the source of truth, and Artisan CLI persists state under the hood.

1 · Skills

/larapilot-* prompts published via Laravel Boost. You invoke them in the editor; the agent reads SKILL.md and follows the execution contract.

2 · Artifacts

PRD, backlog, specs, plans, and review notes live under .larapilot/ in git — durable state the agent reloads on every skill activation.

3 · CLI (under the hood)

Skills call php artisan larapilot:* to persist files and enforce status transitions. You rarely run these directly — see Artisan CLI. After a package upgrade, run Composer + larapilot:update to refresh shared runtime and Boost skills.

What the agent loads — and what it skips

A skill opens with one command, php artisan larapilot:context {skill}. One envelope answers the settings, the paths the skill uses, what the PRD says about the project, and the runtime files to read. Three things keep the context small, and keep an agent from applying a rule that is not the project's:

Rule How
Only what the skill needs Every skill has its own list of packs. runtime.read is what it reads before it starts; runtime.on_demand names the heavy ones — tenancy, CI/CD, integrations, UX, deploy platforms — with the moment that calls for them. /larapilot-triage reads the core only: the branch not taken costs nothing
Only what the settings call for The packs are compiled for the project into .larapilot/cache/runtime/. Under GITFLOW an agent reads the GITFLOW rules and never sees the other two modes; a toggle that is off has no section at all. Change a setting and the next call hands out the files that changed
Only once per conversation The call returns a session token. Passed back with --session=, it tells the next skill of the same conversation which files are already loaded (runtime.loaded): it reads the new ones and nothing else. After the conversation is compacted, --fresh reads everything again — a summary keeps the token and loses the rules
Loaded (skill + runtime, default settings) v4 v5
/larapilot-implement, first skill of a conversation ~43k tokens ~15k
/larapilot-review after implement, same conversation ~38k ~2k
triage → bug → plan → implement → review, one conversation ~173k ~31k

Runtime files are read with the editor's file-read tool, never cat/head/sed; each stays under 15 KB so one read returns it whole, and a truncated preview counts as a failed load. Commands answer with the slice, too: spec-list is the backlog without the bodies (--full for everything), prd-show reads the PRD by the piece (its outline, --ids=FR-004,J-001, --section="Technical Architecture"), and spec-show US-004 --task=TASK-02 one task. Chat stays short; artifacts on disk stay complete.

.larapilot/shared-runtime.md and the runtime-*.md files stay in the repository as the index for people, and as the fallback when the command cannot run. .larapilot/cache/ is derived, ignores itself in git, and is rebuilt whenever it is stale.

Upgrade

Larapilot ships as a Composer package and follows semantic versioning. Every upgrade makes the same two moves: bring the new package into vendor/, then run larapilot:update to refresh the files Larapilot publishes into your project. Your workflow data is never migrated — .larapilot/config.yaml, PRD, backlog, specs, plans, and decisions are read as they are. What the release changes is how you bring it in.

Release Example Bring it in with Read first
Patch or minor 4.1.2 → 4.1.4
5.0 → 5.1
composer update, inside the constraint your composer.json names Nothing — three commands
Major 4.x → 5.0 composer require with the new constraint: composer update never leaves the major, and ^4.0 stays on 4 The Breaking notes in the changelog, then the steps below

Which release is waiting

Terminal — in your Laravel project
composer show andreapollastri/larapilot --latest | grep -E '^(versions|latest)'
# versions : * 4.1.2
# latest   : 5.0.4 released 2026-10-07

composer why andreapollastri/larapilot
# laravel/laravel dev-main requires andreapollastri/larapilot (^4.0)

versions is what the project runs, latest what Packagist has: a different first number is a major. composer why gives the constraint and where it sits — requires is require, requires (for development) is require-dev, where the install command puts it. A major upgrade needs to know which.

Patch and minor releases

Terminal — patch or minor
  1. composer update andreapollastri/larapilot laravel/boost --with-dependencies
  2. php artisan larapilot:update
  3. php artisan larapilot:doctor --human

Commit what changed — composer.lock, the runtime in .larapilot/, the skills and guidelines Boost republished for your editors — then open a new conversation with your agent: the one already open keeps the skills it loaded when it began.

Major releases, step by step

Shown for 4.x → 5.0; for a later major, change the numbers. Work on a branch, so the upgrade is one commit you can read, test, and throw away.

  1. Read what breaks. The Breaking section of the new version in the changelog says which scripts and custom skills may need a change; step 6 finds them. For 5.0, the whole picture is Version 5 vs version 4.
  2. Pick a quiet moment. No /larapilot-implement or /larapilot-autopilot running: the package, the runtime, and the skills would change under the agent halfway through a story. Specs in progress are fine — they are read as they are.
  3. Branch from a clean tree, and rehearse.
    Terminal — rehearsal, writes nothing
    git switch -c upgrade/larapilot-5
    git status --short    # prints nothing: the upgrade will be the only change here
    composer require andreapollastri/larapilot:^5.0 --dev --with-all-dependencies --dry-run

    The dry run resolves the new version and lists every package it would move. It writes nothing — not even composer.json, although it prints that it was updated. --with-all-dependencies lets Composer move what Larapilot depends on, Laravel and Boost included, within the constraints of your composer.json. When it cannot resolve, its message names the package that holds the upgrade back.

  4. Upgrade.
    Terminal — major, 4.x → 5.0
    1. composer require andreapollastri/larapilot:^5.0 --dev --with-all-dependencies
    2. php artisan larapilot:update
    3. php artisan larapilot:doctor --human
    • composer why said requires, not requires (for development)? Drop --dev, from the rehearsal too. With it, Composer moves Larapilot to require-dev — and without asking when it runs non-interactively, as in a script.
    • Edited the packaged design systems? Run php artisan larapilot:update --preserve-design-systems: without the option, the packaged copies overwrite .larapilot/design-systems/.
  5. Read what larapilot:update printed. Most of it reports work done; these lines ask something of you.
    The line says What to do
    config.yaml is missing setting keys … Nothing is broken: the defaults already apply. To keep a choice on record, set it with /larapilot-settings or php artisan larapilot:settings-set.
    Design systems refreshed … Printed on every run without --preserve-design-systems. If you had edited them: git restore .larapilot/design-systems, then php artisan larapilot:update --preserve-design-systems --skip-boost.
    Left in _project_docs … Merge the files it names by hand, then delete _project_docs/; larapilot:doctor warns until it is gone.
    … date(s) do not hold and … finding(s) need a decision Run /larapilot-schedule when you are ready to re-plan: the update changes no priority, blocker, estimate, or date.
    boost:update is not available / failed Not available: Boost is missing — composer require laravel/boost --dev. Then, either way, php artisan boost:install once and php artisan larapilot:update again.
  6. Find what the major breaks — 4.x → 5.0.
    Terminal — 4.x → 5.0 checks
    # scripts and CI — the files Larapilot and Boost publish are left out
    git grep -nE "larapilot:spec-list|larapilot-boogle|larapilot:vps-provision" -- \
      ':!.larapilot' ':!*SKILL.md' ':!CLAUDE.md' ':!AGENTS.md'
    
    # your custom skills
    git grep -nE "spec-list|larapilot-boogle|runtime-[a-z]+-[0-9]+\.md" -- .larapilot/skills

    No output means nothing to change. A hit in another editor's guideline file (.cursor/rules/, .github/copilot-instructions.md, …) is Larapilot's own text, refreshed by the upgrade: skip it. For each other hit:

    • spec-list answers without the bodies now: add --full where the script or skill reads body or status_history.
    • larapilot-boogle is /larapilot-error. The boogle-* commands still answer under their old names.
    • larapilot:vps-provision is gone, with the provision.sh it generated: drop the call. A server already set up with that script is not touched by the upgrade — the prj-* tools it installed never called the package — but nothing maintains them any more: deploy those projects with the platform their PRD records (Forge, Ploi, Cipi, …) through /larapilot-ship.
    • runtime-…-N.md in a custom skill names a part of a pack that v5 re-split by audience: cite the heading instead, or start the skill with php artisan larapilot:context {its name} --with=delivery-1,dev-docs (Custom skills).
    • An external frontend is linked? Run /larapilot-frontend-companion once: in a monorepo it asks which projects are this product's, and it reads the team's agent rules from then on.
  7. Commit, and open a new conversation.
    Terminal — one commit for the upgrade
    git add -A
    git commit -m "Upgrade Larapilot to 5.0"

    The commit holds composer.json, composer.lock, the runtime in .larapilot/, and the skills and guidelines for your editors; .larapilot/cache/ ignores itself. Open a new conversation with your agent — the one already open keeps the 4.x skills it loaded when it began. After the merge, teammates run composer install, and the published files come with the pull; where the editors' skill folders are kept out of git, each teammate also runs php artisan boost:update. larapilot:update stays with whoever makes the upgrade commit: it also runs composer update laravel/boost, which belongs in that commit.

Roll back

Terminal — back to 4.x
# not committed yet? stash the attempt: kept, not lost
git stash push --include-untracked -m "larapilot 5 attempt"

git switch main            # or the branch you started from
composer install           # 4.x back in vendor/
php artisan boost:update   # the 4.x skills back in your editors

The switch brings back composer.json, composer.lock, and .larapilot/ as they were. The upgrade branch stays until you delete it: git branch -D upgrade/larapilot-5.

What each step does

Command Updates Leaves untouched
composer update …
patch or minor
PHP packages in vendor/ — Larapilot (services, CLI, dashboard, design-system packages) and the latest stable laravel/boost, inside your constraints Your .larapilot/ project config and workflow artifacts
composer require …:^5.0
major
The constraint in composer.json, then the same packages, plus whatever Larapilot depends on — Laravel included — as far as your other constraints allow Your .larapilot/ project config and workflow artifacts
larapilot:update
every release
.larapilot/shared-runtime.md and every runtime-*.md pack (and removes the ones the version no longer ships), task-templates.md, integrations.md, packaged .larapilot/design-systems/ references (skip with --preserve-design-systems); re-registers your custom skills with Boost; moves a handbook an older version left in _project_docs/ into .larapilot/docs/handbook/ (files the handbook already holds stay behind, reported); the delivery forecast realigned for a project that is already planned — an id and a plain date on a milestone that lacks them, the release a milestone is named after, the deadline of an epic on its specs that lack it — with one line on what the forecast now misses, for /larapilot-schedule; missing phpstan.neon.dist / pint.json and Composer quality entries; composer update laravel/boost (latest stable) then boost:update to republish guidelines and /larapilot-* skills .larapilot/config.yaml (except an old paths.project_docs: _project_docs/, repointed), PRD, backlog, specs, plans, mockups, domain docs, custom skills, and other project-owned files

Options & checks

  • php artisan larapilot:update --skip-boost — refresh shared runtime and design-system copies only (does not bump laravel/boost or republish skills); run composer update laravel/boost --with-dependencies and php artisan boost:update yourself when you want the latest Boost
  • php artisan larapilot:update --preserve-design-systems — keep .larapilot/design-systems/ as it is, your edits included
  • php artisan larapilot:doctor --human — verify install health after an upgrade, as a table (includes Larastan/Pint gate); without --human it answers JSON for scripts
  • php artisan boost:update --discover — one-time if Boost was installed before Larapilot and skills are missing

Automate with Composer (optional)

Add a post-update-cmd hook so larapilot:update runs whenever Larapilot is updated via Composer:

composer.json — scripts
"scripts": {
    "post-update-cmd": [
        "@php artisan larapilot:update --skip-boost"
    ]
}

Keep --skip-boost in the hook so Composer does not recurse into composer update laravel/boost. Omit it only when you want Boost guidelines and skills republished on every Composer update (the package bump is skipped automatically inside a Composer script). Run php artisan boost:update manually when you use --skip-boost in the hook, or include laravel/boost in the Composer update command so the lockfile tracks latest. The hook keeps patch and minor releases in step; a major still takes the steps above.

Do not re-run larapilot:install on an existing project unless you intend to scaffold from scratch (--force). Use larapilot:update for routine upgrades and majors alike.

Skills

Twenty-nine skills cover the life of a product: discovery, delivery, changes after launch, security and upgrades, ship, and the tools around them. Each one is a SKILL.md file that names the personas who speak, the steps they follow, and the commands they may call. Laravel Boost publishes them into your editor on php artisan boost:install.

Your own skills live in .larapilot/skills/ — see Custom skills. A short Boost guideline ships alongside: it tells the agent what Larapilot is and which skill to reach for.

When you type /larapilot-plan US-001, the agent:

  1. Loads the skill instructions and runs larapilot:context plan: settings, paths, and the runtime files this skill reads — minus the ones the conversation already loaded
  2. Reads current artifacts (PRD, spec, backlog status)
  3. Runs the guided conversation with the right personas
  4. Persists output via Artisan commands defined in the skill — never inventing its own file writes

The skills below are grouped by when you reach for them: the core loop runs on every story; the other groups plug in around it.

Core loop

Runs on every story: idea → PRD → backlog → plan → code → human gate.

discovery

/larapilot-inception

A guided product interview that ends with PRD.md. It is run as a conversation, not a questionnaire: every answer gets a reaction before the next question, and AskQuestion is used only for the fixed choices Larapilot stores, three at most per round, all skippable. It goes through these steps, in order:

  1. The goal is challenged — at least two exchanges before any requirement is written: who has this problem and what they do instead, what changes if it works, how you will know in 90 days, the riskiest assumption, what would make you stop
  2. Four rounds always happen, legacy rewrites included: Project Kind (Personal · Website · Application · Package), Delivery Target, Business Model, Operations & support. A skipped round is written as Not decided, never as a guess, with what skipping it costs
  3. Prior-art check (prior_art, on by default) — Sebastian states his queries, asks your consent, searches for products and packages that already do it, writes research/prior-art.md, and you record the verdict: Build anyway · Adopt / fork · Integrate as dependency · Not checked
  4. The product is written down — User Journeys, Domain Model, every requirement with a named actor and verifiable Done means, quality targets with a number and a verifier, Risks & Assumptions. Each section is explained in The PRD
  5. Architecture choices are asked, never assumed — frontend topology, admin panel, data store, local development, deploy platform. Lucille asks for deadlines; on a new project one question offers release mode
  6. Ready check and readback — Tom runs a ten-point Definition of Ready, Mark reads the decisions back in at most twelve lines, and the PRD is written once, after you confirm

It reads client-materials/ and legacy/ first when they hold files. The Package kind adds its own questions: origin, distribution, consumer install, CI, docs. When a PRD already exists, inception does not overwrite it — it offers a revision through /larapilot-prd, or a pivot.

CLI: larapilot:prd-write · larapilot:validate-prd · larapilot:choices-set · larapilot:schedule-set.

backlog

/larapilot-spec

Turns the PRD into a backlog of user stories: backlog.yaml plus one specs/US-XXX.yaml each. On an empty backlog it builds all of it; on an existing one it adds only what is missing.

  • One story per journey by default. The backlog setting makes stories coarser or finer
  • How much is built follows the MoSCoW tags and the delivery target: an MVP gets the Must requirements, a V1 adds the Should, a full product adds the Could. Won't never gets a story
  • Acceptance criteria come from each requirement's Done means, the journey's failure modes, and the quality targets that apply
  • A requirement blocked by an open question gets its story with the question written in it — never a guessed answer
  • It ends with a coverage check: every Must requirement has a story, or a stated reason why it waits

CLI: larapilot:spec-add · larapilot:validate-spec · larapilot:spec-list · larapilot:prd-impact.

plan

/larapilot-plan US-XXX

Technical plan with tasks, test strategy, Git deliverables → plans/US-XXX-plan.yaml. Status → PLANNED. On a large codebase, an optional read-only explore sub-agent maps the code first (never under effort: ECO). When autopilot runs this step, that mapping stays inside the spec worker — no nested sub-agent.

CLI: larapilot:validate-plan · larapilot:spec-plan — validate then persist the plan.

build

/larapilot-implement US-XXX

Executes the plan: Laravel code, Pest tests, per-task commits, and the developer domain doc for every domain the task touched (English, in the same commit). Runs larapilot:quality (Pint + Larastan level 5+) on backend tasks before task-done. After the tasks, Robert and Lars review the diff in parallel (inline under ECO). Status → REVIEW when all tasks complete. Autopilot runs only Phase 1 in the worker; the parent session launches that review and calls spec-review.

CLI: larapilot:spec-start · larapilot:quality (--fix) · larapilot:task-done · larapilot:spec-review.

human gate

/larapilot-review US-XXX

Human gate. You approve → DONE, or send back with feedback → TODO.

CLI: larapilot:spec-approve or larapilot:spec-request-changes.

Onboarding — no PRD yet

Pick the one that matches where your project stands, then continue with the core loop.

brownfield

/larapilot-adopt

Brings an existing, running Laravel app — built without Larapilot — under the workflow. Reverse-engineers a complete PRD from the code itself: models & migrations, routes/controllers, jobs, policies, config, packages, CLI/scheduler, CI, and frontend surfaces. Personas + functional requirements are inferred from shipped behavior with file-path evidence; the agent asks only the gaps the code can't answer.

  • Runs right after php artisan larapilot:install, when there is no PRD
  • Writes .larapilot/research/codebase-analysis.md + the PRD; records Project Origin: Adopted (existing codebase) — scoped like Greenfield (no legacy parity contract)
  • Optional read-only Explore sub-agent for large repos (never under effort: ECO)
  • Not for greenfield ideas (/larapilot-inception) or rewrites away from a non-Laravel system (/larapilot-inception + .larapilot/legacy/)
  • When release_mode=YES: release-import rebuilds shipped releases from Git tags; AskQuestion for current production version and in-progress release branches

CLI: larapilot:context · larapilot:prd-write · larapilot:validate-prd · larapilot:choices-set · larapilot:release-import · larapilot:release-add. See the adoption walkthrough and /larapilot-release.

greenfield

/larapilot-inception (idea, or legacy rewrite)

Start here for a new product idea, or to rewrite / port away from a legacy non-Laravel system — drop snapshots in .larapilot/legacy/ and Sabrine leads a parity contract. Full card above under Core loop.

Shortcuts

Skip the full discovery loop for small, well-scoped work on a product that already has a PRD.

one feature

/larapilot-feature "…"

Mini-inception for one enhancement on a shipped product → new US-XXX spec (+ optional PRD FR). Criteria are built from the FR's done-means, the journey's failure modes, and the NFR targets that apply; Tom runs a five-point ready check and Mark reads the feature back before it is persisted. A change request edits the FR in place — same id, before → after in the revision history — and prd-impact names the other stories resting on it. When release_mode=YES and open releases exist, AskQuestion assigns the spec to a release (or backlog) → **Release:** x.y.z line + release-set --add-spec=.

CLI: larapilot:spec-add · larapilot:validate-spec · larapilot:release-list · larapilot:release-set --add-spec= — optionally prd-write when a new FR is added.

bug triage

/larapilot-bug "…"

Sophia-led triage → fix spec or rework route. PRD updated only on requirement gaps. The agent reads the logs of the application every time, whatever the report says — larapilot:logs, the repeats counted and the secrets redacted — and quotes the exception, its place, and how many times it was thrown.

Every fix quotes the promise broken — an FR done-means, an acceptance criterion, or an NFR target — so "fixed" has a definition. The route follows the status of the story: spec-request-changes in REVIEW only, a re-issue through spec-add for TODO and PLANNED, a new fix spec for DONE. A defect already in the intake log gains an occurrence, not a second story.

CLI: larapilot:spec-add or larapilot:spec-request-changes — plus larapilot:logs every time, larapilot:diagnostics for the health of the runtime, and larapilot:prd-impact on a requirement gap.

bug or feature?

/larapilot-triage "…"

Front door for a request you cannot place. Applies the promise test: a bug when an FR or an acceptance criterion promised the behavior (or it worked before), a feature when nothing did — a change to what was agreed included. States the verdict with its evidence in one line, then runs /larapilot-bug or /larapilot-feature in the same turn, with the answers it already has. Asks one question only when the evidence does not settle it. Writes no spec itself. Both target skills run the same check on their own evidence and hand over to each other once when the label was wrong. Walkthrough.

CLI: larapilot:spec-list · larapilot:spec-show US-XXX --fields=id — larapilot:decision-log when you settle the verdict.

PRD revision

/larapilot-prd "…"

Changes the PRD when the change is neither a bug nor a feature. Mark names the revision kind — editorial, sharpen, re-scope, re-model, re-decide, upgrade — the team interviews in proportion to it, Tom traces the touched ids to the stories that cite them, and Mark reads the delta back before anything is written. Ids are never renumbered or reused; a dropped requirement is retired with its heading kept. Also reconciles a PRD you edited by hand and brings an older PRD up to the current shape. Walkthrough.

CLI: larapilot:validate-prd · larapilot:prd-impact (--ids=) · larapilot:prd-write · larapilot:choices-set --from-prd · larapilot:spec-add / spec-request-changes / spec-delete to align the stories.

Optional steps

Insert into the loop when the work calls for it — none are required to ship.

mockups

/larapilot-design US-XXX — before plan

Static HTML mockups in mockups/{spec}/, styled from packaged references — Filament, Starter Kit, Bootstrap 5, Tailwind, or AdminLTE per PRD. Preview at /mockups/{spec} (non-production), on the spec page, and in the dashboard Design gallery (/larapilot/design) — every screen as a card you click to browse the mockup like a site, zip download of HTML + assets. A mockup never contains a field for a username or a password: it draws them.

CLI: larapilot:context — resolve paths.mockups and paths.design_systems.

batch delivery

/larapilot-autopilot US-XXX … — many stories at once

Chains plan + implement for multiple specs, one at a time, on the same working tree. Under STANDARD or MAX each spec gets two writing sub-agents in sequence — one for the plan, one for implement Phase 1 — and then this session takes over: spec-plan, spec-start, task-done, the Robert/Lars review, and spec-review. The worker cannot ask you a question and cannot change workflow state; a missing decision comes back as BLOCKED and this session asks. This session does not load the delivery packs, domain-doc rules, or the PRD; the delivery target is choices.yaml. Robert and Lars return at most 8 bullets, and the diff stays in the review file. ECO, and an editor with no sub-agent tool, run plan and implement inline. By default you still run /larapilot-review per story; with auto_approve: YES it may spec-approve after implement.

CLI: same as plan/implement; plus larapilot:spec-approve when auto-approve is on.

ship

/larapilot-ship — when the MVP is DONE

OWASP security gate + multi-platform deploy runbook when target stories are DONE. With release_mode=YES, Sarah runs the release ship ceremony (merge release/x.y.z → main, tag vX.Y.Z, back-merge → develop) before or alongside deploy.

CLI: larapilot:context · larapilot:metrics · larapilot:release-list --status=in_progress when release mode is on.

Project setup & ops

Configure how Larapilot works on this project, and follow effort over time.

config

/larapilot-settings

AskQuestion for persistent project settings, written to .larapilot/config.yaml:

  • Process — effort, backlog granularity, git_mode, testing, auto_approve, lucille
  • Traceability — decision_log (journal + regression guard, ON by default), code_history (per spec/task file+line log, OFF)
  • Discovery — prior_art (Sebastian's existing-solutions search at inception, consent asked before every search, ON)
  • Delivery extras — release_mode (semver ledger + Gitflow release branches, OFF), project_docs (living handbook in .larapilot/docs/handbook/, OFF)
  • Business — account (NONE · FREELANCE · COMPANY; anything but NONE unlocks Economics)
  • Access & security — comments (dashboard + API internal feedback), dashboard_auth (HTTP Basic Auth on the dashboard UI), api_auth (mandatory LARAPILOT_API_TOKEN on the JSON API), security_scan (checkpoint in review and pre-ship) — all OFF by default
  • Integrations — GitHub / GitLab / Bitbucket / Azure DevOps forges, Slack / Discord / Telegram channels

Full tables in Settings.

CLI: larapilot:settings-set — write settings; larapilot:context — read data.settings.

semver

/larapilot-release — requires release_mode=YES

Manage the semver release ledger (.larapilot/releases.yaml): roadmaps, register releases, assign specs, switch active release/* branches, import history from Git tags, move status toward ship. Sarah owns Git mechanics; Jack owns policy; Mark owns scope.

CLI: larapilot:release-list · larapilot:release-add · larapilot:release-set · larapilot:release-cut · larapilot:release-feature · larapilot:release-sync · larapilot:release-ship · larapilot:release-import.

Full reference

Opt-in — enable with settings.release_mode: YES (/larapilot-settings or larapilot:settings-set --release-mode=YES). When OFF, Larapilot keeps classic develop + feature/US-XXX-* only.

  • Ledger at .larapilot/releases.yaml — semver, title, status, branch, assigned specs, timestamps.
  • Wired into /larapilot-inception, /larapilot-adopt, /larapilot-feature, /larapilot-plan, and /larapilot-ship.
  • With Gitflow: specs assigned to a release branch from release/x.y.z (TASK-00 variant in task-templates.md).
  • Parallel releases — Sarah confirms the active release branch when several are open.
Status Meaning Next step
planned Registered, scope negotiable Assign specs → in_progress
in_progress Actively built on release/x.y.z Ship ceremony → shipped
shipped On main, tagged vX.Y.Z Terminal — new row for follow-up work

Ship ceremony (Gitflow + release mode): php artisan larapilot:release-ship --semver=x.y.z merges release/x.y.z into main, tags vX.Y.Z, back-merges into develop, and marks the ledger shipped. Feature work on that release is larapilot:release-feature --spec=US-XXX. release-list reports git.needs_choice so a branch is picked only when more than one release is in progress. Use --semver=, not --version= (Artisan reserves --version). Add --push only when pushing is intended. Runtime pack: .larapilot/runtime-release.md.

handbook

/larapilot-project-docs — requires project_docs=YES

Albert maintains a living technical + functional handbook under .larapilot/docs/handbook/ — chapters, Mermaid diagrams, API/CLI/architecture coverage. Bootstrap retroactively when enabled mid-project; incremental updates after implement/review/ship.

CLI: larapilot:context · larapilot:spec-list — no dedicated write command; the skill updates Markdown chapters directly.

Full reference

Enable with settings.project_docs: YES (larapilot:settings-set --project-docs=YES). Default path: .larapilot/docs/handbook/, seeded on install with a README.md that the bootstrap replaces with the index. A handbook an older version wrote in _project_docs/ at the repo root is moved there by larapilot:update.

.larapilot/docs/handbook/ ├── README.md # index (a stub until the bootstrap) ├── 01-overview.md ├── 02-architecture.md ├── 03-features/ ├── 04-api.md ├── 05-cli-and-jobs.md ├── 06-frontend.md ├── 07-operations.md ├── 08-releases.md # when release_mode=YES └── diagrams/

After material changes: implement → feature chapter; review/ship → operations/releases. Never store secrets or user-specific paths — env var names only. Mid-project bootstrap: Albert scaffolds from PRD, specs, plans, and git history; marks uncertain blocks <!-- TODO: verify -->. Runtime pack: .larapilot/runtime-project-docs.md.

extend

/larapilot-custom-skill

Turns a team ritual into your own slash command — a deploy gate, a compliance check, client release notes, an internal runbook. Zoey interviews you in three rounds (one skill or a family · slash name and trigger description · personas, runtime packs, and CLI calls), Albert tightens the steps, and Sarah saves the result with larapilot:custom-skill-add. The skill lands in .larapilot/skills/{name}/SKILL.md, is registered with Boost in the same call, and shows up on the dashboard Skills page.

Walkthrough: Your own skill · Contract and lifecycle: Custom skills.

CLI: larapilot:custom-skill-list · larapilot:custom-skill-add (--name=, --file= / --content= / stdin, --force).

economics

/larapilot-economics

Aurora calibrates country + tax regime (forfettario, SRL, Ltd, …) and produces a quote, net-to-owner after FY-2026 rates, payback, and — for SaaS — ARR, break-even customers, hosting, and a 36-month forecast. Requires settings.account FREELANCE or COMPANY. Hours come from the backlog spec by spec (planned task hours, then story points, with hours-per-point calibrated on the plans you already wrote), and the snapshot refreshes itself whenever specs, plans, the PRD, or inception change.

/larapilot/economics is an interactive pricing tool: dropdowns for hourly rate, margin, commercial discount, team size, account type, country, regime, monthly price, BASE / PRO / PREMIUM price line and market scenario recompute every figure, chart and the quote download live — server side, through the same snapshot the CLI uses. Nothing is written from the browser: a simulation prints the economics-set command that would make it real. A discount comes out of the margin and flags the project when it drops below cost; team size compresses the timeline and never the price.

Sold as decides which half of the page is the real one, without ever moving the hours or the build price: fixed (one shot — the client pays once, payback is read as capacity), saas (subscription — packaging, break-even customers, a 36-month business plan), ecommerce (one-shot price, payback read in orders per month), and package (one-shot price, payback read in licences). Left on auto it follows the Business Model answer from inception. Each one is explained in place on the page, and in full under Sold as.

For a subscription product the page carries three price lines with the slice of the backlog each one includes, and a business plan read three ways — pessimistic, realistic, optimistic, 36 months each. Jennifer and Benjamin research the market during the skill (sector, named competitors with their price and trend, demand, packaging) and persist it with larapilot:economics-market-write; the dashboard plots it and shows where your price sits against the researched set. Larapilot never invents a competitor or a price. On a plain one-off client delivery that research step is skipped unless you ask for it — the page says so instead of pointing at a command that would do nothing.

The page itself reads in the PRD's language (en · it · es · fr · de · pt · nl · pl, English otherwise), like the client quote — headings, captions, warnings and glossary. The pricing console stays in English: its labels and the economics-set command it prints are operator controls.

Aurora also writes the client quote — a commercial document in the PRD's own language, any language — to .larapilot/docs/quote.md, served at /larapilot/economics/quote.md and by the dashboard's Download quote button. A built-in en · it · es · fr · de · pt · nl · pl template covers projects where nobody wrote one yet. Internal tax report: /larapilot/economics/report.md.

CLI: larapilot:economics-set · larapilot:economics-show (--format=json|md|quote) · larapilot:economics-quote-write (--file=, --lang=) · larapilot:economics-market-write (--file=) · larapilot:settings-set --account=….

time & tokens

/larapilot-usage

Lucille interrogates the committed time/token ledger and schedule — overview, filters by category/user/skill/spec/date, deadline drift, and Markdown report export. Read-only while settings.lucille is NO. Full reference: Usage & Lucille.

CLI: larapilot:usage-report · larapilot:usage-log · larapilot:schedule-set.

re-plan

/larapilot-schedule

Lucille puts a project that is already planned back in line with its delivery forecast. She reads the queue of the open specs and what the forecast could not read in its inputs — a blocker that is not in the backlog, a spec with no points, tasks with no estimate, a milestone that says on track and is not — then settles with you, date by date, what to do with each one the forecast misses: move it, reorder, defer scope, or flag it. Every change is forecast with a dry run and shown before → after; nothing is written until you say so. Run it once on a project planned before v5.0.0, when every spec started on the first day.

  • larapilot:schedule-show — the queue in delivery order with its dates, epics, milestones and releases against the forecast, alerts, findings
  • larapilot:schedule-apply --file=replan.json --dry-run — priorities, points, blockers, task hours and assignees, epic deadlines, milestones, in one batch
  • larapilot:schedule-apply --repair — what takes no decision; run by larapilot:update

CLI: larapilot:schedule-show · larapilot:schedule-apply.

Integrations

Connect Larapilot to the tools your team already uses. All OFF until configured — see Forges & notify.

frontend repo

/larapilot-frontend-companion

For API + external frontend: link the FE absolute path (stored in LARAPILOT_FRONTEND_REPO_PATH in .env, never committed in YAML), name the projects of this product when it is a monorepo (Nx, Angular CLI, pnpm / yarn workspaces, Turborepo), and scan: workspace, the team's agent rules (AGENTS.md, CLAUDE.md, Cursor, Copilot, …), the conventions measured on the code, the commands, the API client. All delivery stays on Laravel via repo: frontend tasks — or goes to the frontend team as a brief. External frontend repo.

  • larapilot:frontend-set --path=/absolute/path/to/fe-repo --project=portal
  • larapilot:frontend-scan · larapilot:frontend-rules --file=…
  • larapilot:frontend-set --mode=handoff · larapilot:frontend-brief US-012

CLI: frontend-set · frontend-scan · frontend-rules · frontend-brief.

dev portal

/larapilot-backstage

Publishes the repo into a Backstage developer portal — catalog entity (catalog-info.yaml) plus TechDocs generated from PRD and backlog. Asks for owner, system, and lifecycle, then generates. Full reference: Backstage portal.

CLI: larapilot:backstage-export (preview) · --write to generate.

tracker sync

/larapilot-tracker

Mirrors the backlog into Linear, Asana, Jira, Trello, ClickUp, or Monday over an API key. Stories become issues, plan tasks become native subtasks, and remote status comes back as a drift report. Full reference: Project trackers.

CLI: larapilot:tracker-status · larapilot:tracker-push · larapilot:tracker-pull.

security findings

/larapilot-aikido

Downloads the open findings Aikido has for the repository, shows the ones nobody decided about, and hands each one you pick to /larapilot-triage, which routes it to /larapilot-bug or /larapilot-feature. It runs no scanner and writes no spec itself. Needs aikido=YES. Full reference: Aikido.

CLI: larapilot:aikido-status · larapilot:aikido-issues · larapilot:aikido-plan · larapilot:aikido-link · larapilot:aikido-push · larapilot:aikido-repos · larapilot:aikido-register · larapilot:aikido-scan.

production errors

/larapilot-error

Asks which tracker records the errors of production when none is set — Boogle, Sentry, Bugsnag, Flare, Datadog, Rollbar, Honeybadger, or CloudWatch — and turns the errors on. Then it downloads the open ones, has you confirm each bug, groups the confirmed codes with larapilot:errors-plan, and hands each group to /larapilot-triage, which routes it to /larapilot-bug or /larapilot-feature. It writes no spec itself, and it never reads who the user of a request was. Full reference: Production errors.

CLI: larapilot:errors-status · larapilot:errors-list · larapilot:errors-plan · larapilot:errors-link · larapilot:errors-resolve.

Upgrades & dependencies

Move the stack forward and keep the dependencies clean. Each upgrade skill starts with a readiness report and changes nothing until you choose — see Laravel, PHP & DB upgrades and SBOM, CVE & Checkpoint.

laravel

/larapilot-laravel-upgrade

From the Laravel the project is on to the one you name. Andrew runs the readiness check and the Composer dry run, shows the criticalities, then — when you say upgrade now — goes one major at a time on its own branch: the Composer change, the upgrade guide of that version (read through Boost Search Docs), Filament's upgrade script, livewire:upgrade, Inertia server and adapter together, Nova's guide, the gates, one commit per step. Ends with an upgrade report, deploy runbook, and rollback.

CLI: larapilot:upgrade-check --laravel=13 --report · larapilot:stack.

php

/larapilot-php-upgrade

The suite on the target PHP first, then the packages that exclude it, the code it deprecates (Rector, PHPStan on the target), every file that pins PHP — Docker, CI, Vapor, Herd, Sail, config.platform.php — and the runbook for Forge, Cloud, Vapor, or a VPS.

CLI: larapilot:upgrade-check --php=8.4 (--php-from=).

database

/larapilot-db-upgrade

MySQL 5.7 → 8.0 → 8.4, MySQL → MariaDB or PostgreSQL, a PostgreSQL major, SQLite → a server. Mike makes raw SQL, migrations, and configuration portable, proves it on a local rehearsal against a scratch database, and writes the data move and the cutover. It never touches a production or shared database.

CLI: larapilot:upgrade-check --db=pgsql:17 --db-from=mysql:8.0.

CVE

/larapilot-vendor-check

Every package of the SBOM — Composer, the JavaScript of the repository, the frontend companion — against OSV.dev. You decide package by package: update now (its fix command, the suite, one commit), backlog (to /larapilot-triage), or waive with a reason. The result is on the SBOM page.

CLI: larapilot:sbom (--write=both) · larapilot:vendor-audit (--gate) · larapilot:vendor-link.

Custom skills

The twenty-nine packaged skills are the base layer. On top of them, every project can keep its own Boost skills in .larapilot/skills/ — committed with the code, registered with Boost automatically, and built on the same CLI the packaged skills use. They extend the loop; they never replace it. Walkthrough: Your own skill.

Good candidates

  • Gates — pre-deploy GO/NO-GO, a hotfix checklist, a "ready for client demo" check
  • Compliance — GDPR data-map refresh, an accessibility pass, a licence audit of new dependencies
  • Communication — client release notes from the stories DONE since the last tag, a weekly status digest from metrics and usage-report
  • House rules — how your team scaffolds a Filament resource, names queues, or wires a webhook

If it changes workflow state (a spec's status, the backlog, the PRD), it belongs in a packaged skill or its CLI — a custom skill calls those commands, it does not reinvent them.

Anatomy of a SKILL.md

.larapilot/skills/{name}/SKILL.md
---
name: acme-release-notes                 # kebab-case, 1–63 chars — also the slash command
description: "Client release notes from the stories DONE since the last tag. Use when someone asks
  what shipped, for a changelog, or for notes to send the client."   # required — Boost routes on it
---

# Acme — Release notes                   # optional title, shown on the dashboard Skills page

One paragraph on what it does — shown on the Skills page when there is no description.

## Context                               # larapilot:context {name} — settings, paths, files to read
## The Team                              # personas who speak, from the 30-persona roster
## CLI                                   # the larapilot:* commands the skill calls
## Workflow                              # numbered steps, each naming its larapilot:* command
## Output Economy                        # how much goes in chat vs on disk

Three ways to save one

Terminal
# 1. Guided — Zoey interviews, Sarah saves (recommended)
/larapilot-custom-skill

# 2. From a file you wrote — name taken from --name, or from the front matter
php artisan larapilot:custom-skill-add --file=path/to/SKILL.md
php artisan larapilot:custom-skill-add --name=acme-release-notes --file=path/to/SKILL.md --force

# 3. Piped from stdin (or inline with --content=)
cat SKILL.md | php artisan larapilot:custom-skill-add --name=acme-release-notes

What custom-skill-add checks and does

Step Behavior
Name --name wins over the front matter and is written back into it. Must match ^[a-z0-9][a-z0-9-]{0,62}$, otherwise E_INVALID_INPUT (exit 2)
Front matter Front matter with an empty or missing description is refused. A file with no front matter at all gets one, with a placeholder description (Custom Larapilot skill.) — replace it, since Boost routes on that line
Overwrite An existing skill with the same name is refused unless you pass --force
Save Atomic write to .larapilot/skills/{name}/SKILL.md (path key paths.custom_skills)
Register Mirrors the whole folder into .ai/skills/{name}/ — always — and into each agent skill folder that already exists: .claude/skills/, .cursor/skills/, .github/skills/, .agents/skills/, .codex/skills/, .gemini/skills/, .junie/skills/, .opencode/skills/, .windsurf/skills/
Publish Runs boost:update when Boost is installed; the envelope reports data.boost_published

A skill folder can carry more than SKILL.md — a template, a checklist, a script. Everything in the folder is mirrored.

Lifecycle

Moment What happens
larapilot:install Creates .larapilot/skills/ with a .gitkeep, so the empty folder is committed
custom-skill-list · larapilot:update · opening /larapilot/skills Re-register every skill found on disk (copy into .ai/skills/ and existing agent folders). This is how a teammate picks up skills after git pull
Edit Re-save with custom-skill-add --force, or re-run /larapilot-custom-skill
Remove Delete .larapilot/skills/{name}/ and its mirrors in .ai/skills/ and the agent folders, then php artisan boost:update. Registration adds and refreshes copies; it never deletes one

Quality bar

  • Start with php artisan larapilot:context {name} (add --with=delivery-1,dev-docs for the packs it needs), read what it lists, and honor data.settings (effort, git mode, security scan, notifications…) like a packaged skill.
  • Persist only through larapilot:* commands. New files go under existing .larapilot/ paths — never ad-hoc YAML beside the workflow artifacts.
  • Reuse persona names from the roster (config-show --only=personas).
  • No machine-specific absolute paths — use env vars, as in Environment paths.
  • Prefix the name with your team or client (acme-) so it never collides with a packaged larapilot-* skill.

Runtime pack: .larapilot/runtime-custom-skills.md. There is no setting to turn custom skills on — once saved, a skill is available through its slash command.

Workflow hooks opt-in

A story always moves through the same transitions — planned, started, task done, review, approved or sent back, released, shipped. Hooks attach your team's own commands and skills to those moments. They live in .larapilot/hooks.yaml, committed so the team shares them, and run only while settings.hooks is YES.

.larapilot/hooks.yaml
hooks:
  task.done:
    before:
      - name: Tests
        run: php artisan test --compact
        timeout: 900
  spec.review:
    before:
      - name: Static analysis
        run: vendor/bin/phpstan analyse --no-progress
      - skill: acme-a11y-check
  spec.approved:
    after:
      - name: Deploy to staging
        run: curl -fsS -X POST "$FORGE_STAGING_DEPLOY_URL"
  ship:
    before:
      - skill: acme-predeploy-gate
    after:
      - name: Deploy to production
        run: php vendor/bin/envoy run deploy

Events

Event Fired by Moment
prd.writtenprd-writeThe PRD is saved
spec.addedspec-addStories enter the backlog
spec.plannedspec-planA plan is saved: PLANNED
spec.startedspec-startPLANNED → IN PROGRESS
task.donetask-doneOne task of the plan is done
spec.reviewspec-reviewIN PROGRESS → REVIEW
spec.approvedspec-approveREVIEW → DONE
spec.changes_requestedspec-request-changesREVIEW → TODO, with rework
release.shippedrelease-shipThe release is merged and tagged (release_mode)
ship/larapilot-ship, through hook-run shipbefore as the gate starts, after on a GO

Phases and kinds

  • before runs after the command's own checks and before anything is written. A hook that fails — an exit code other than 0, or past its timeout — refuses the transition with E_PRECONDITION and details.hooks, and nothing is written. blocking: false turns it into a warning.
  • after runs once the state is written. A failure is reported under data.hooks.after.warnings; the transition stands.
  • run: — Larapilot runs the command from the project root, with LARAPILOT_HOOK_EVENT, _PHASE, _SPEC, _SPEC_TITLE, _TASK, _STATUS_FROM, _STATUS_TO, _COMMIT, _RELEASE in its environment and the event as JSON on stdin. The answer carries the last 40 lines of its output; the whole of it is in .larapilot/cache/hooks/.
  • skill: — the agent runs that skill, often one of your custom skills. Before a transition, the command refuses until the agent reports it run with --skill-hooks-done=name; after one, the answer lists it under data.hooks.after.skills.

What to hook

  • Gates an agent cannot skip — the test suite, Larastan, npm run build, composer audit, a coverage floor, before task.done or spec.review. An instruction in a prompt can be forgotten; a hook runs inside the command.
  • Deploys — staging after spec.approved, production after a GO from /larapilot-ship or after release.shipped.
  • Your rituals at the right moment — an architecture review after spec.planned, an accessibility pass before spec.review, client release notes after release.shipped.
  • What Larapilot does not integrate — Teams, Mattermost, or email; a time tracker on spec.started; a wiki export after prd.written; an n8n, Zapier, or Make webhook.
Terminal
php artisan larapilot:settings-set --hooks=YES
php artisan larapilot:hook-list
php artisan larapilot:hook-run task.done --phase=before --spec=US-001 --task=TASK-01 --dry-run

Guarantees

  • Nothing runs until you say so. Install writes hooks.yaml with every event listed and every example commented out, and hooks is NO. LARAPILOT_HOOKS_ENABLED=false in .env runs none on one machine.
  • A file with errors is a closed gate. While hooks are on, every transition is refused until hook-list is clean or hooks are off; doctor reports checks.hooks.
  • Artisan only. Hooks never run from the dashboard, the API, or MCP, where hook-list is read-only. /larapilot/settings lists the hooks of the project.
  • The hooks are yours. Agents never edit a hook or turn hooks off to get past one unless you ask, and --force on spec-review / spec-approve never skips a hook.
  • Secrets stay in .env. A hook reads them from its environment. Timeout 300 s by default (LARAPILOT_HOOKS_TIMEOUT), at most 3600 per hook.

Runtime pack: .larapilot/runtime-hooks.md, read by the skills that move a spec, write the PRD, or ship, while hooks is YES.

Project settings

Persistent project modes — how much effort the agent spends, how the backlog is cut, how Git and tests behave, and which opt-in extras are on. Configure with /larapilot-settings (AskQuestion) or larapilot:settings-set. Values live under settings: in .larapilot/config.yaml, which is committed, so the whole team works in the same mode. Every skill reads them first via larapilot:context.

Group Keys Default
Process effort · backlog · git_mode · testing · auto_approve STANDARD · STANDARD · GITFLOW · NORMAL · NO
Tracking lucille · decision_log · code_history YES · YES · NO
Discovery prior_art YES
Business account NONE
Delivery extras release_mode · project_docs NO · NO
Automation hooks NO
Access & security comments · dashboard_auth · api_auth · security_scan NO for all four
Integrations github · gitlab · bitbucket · azure · notifications · notify_slack · notify_discord · notify_telegram NO for all

The developer domain docs have no setting on purpose: they are always written.

Set one or several at once: php artisan larapilot:settings-set --effort=MAX --testing=BEST. Read them back with php artisan larapilot:config-show --only=settings. Yes/no settings are stored as booleans in the YAML and reported as YES / NO. A setting that is missing from the file takes its default. Every flag is listed at the end of this chapter.

Effort

Value Behavior
ECO Token economy — never spawn sub-agents, including the autopilot spec worker (plan and implement stay in the parent session); disables Lucille automatically (re-enable via settings-set --lucille=YES without leaving ECO); defer docs theater (README/PDF/diagrams) but still update OpenAPI when APIs change and still write the developer domain docs; skip deep reviews and E2E planning
STANDARD Normal Larapilot behavior (default). Autopilot delegates each spec to a writing worker, one at a time; explore during plan and the Robert/Lars review stay available
MAX Deep mode on every flow — fuller persona rounds, always run explore/review sub-agents when available, the autopilot spec worker on every batched spec, richer plans and residual-risk notes

Backlog granularity

Value Behavior
LEAN Fewest specs — one per end-to-end user journey, related FRs merged (each cited as Traces to: FR-XXX); technical seams, admin entities, and i18n locales are always plan tasks; ≤ 5 epics per product
STANDARD One spec per demonstrable user capability (default) — closely related FRs may share a spec; Laravel seams (models, controllers, policies, UI, API resources) become plan tasks; reuse existing epics
GRANULAR Fine-grained — one spec per FR allowed; seam / Filament per-entity / i18n per-locale splits allowed; multi-epic backlog expected. For large teams or spec-per-PR workflows

Granularity changes spec cardinality only — never coverage: merged scope stays traceable via FR-XXX citations in spec bodies and plan tasks. Epics are always consolidated: reuse existing EP-XXX before proposing a new one.

Git mode

Value Behavior
NO_GITFLOW No Gitflow ceremony — work on the current branch; no mandatory feature branch / internal PR
GITFLOW Feature branches + atomic commits + PR prepared locally — no automatic push (default)
GITFLOW_PUSH Full Gitflow with push and open/update of the internal PR toward develop after each task

Push is never implied by GITFLOW alone. See also Git workflow.

Testing

Value Behavior
MINIMAL Critical-path Pest/PHPUnit only — no Playwright, Dusk, browser E2E, or viewport matrix
NORMAL Standard feature/unit/policy/API tests (default) — still no Playwright/Dusk/E2E
BEST Full bar — integrations, primary-journey E2E, Playwright or Dusk / Pest browser, viewport matrix (375 / 768 / 1280), axe, Lighthouse when applicable

Auto-approve

Value Behavior
NO Human gate required (default) — specs stop at REVIEW; only you Approve via /larapilot-review
YES After implement reaches REVIEW, /larapilot-autopilot may present a short checklist and call spec-approve without waiting — opt-out of the usual human-in-the-loop DONE gate

Lucille

settings.lucille controls the silent usage ledger and schedule interviews. Missing key = ON. Exclusion is opt-out only.

Value Behavior
YES Default — log tokens/time at skill end, ask deadlines at inception, surface schedule drift, honor /larapilot-usage
NO Excluded — no usage-log, no Lucille interview rounds; /larapilot-usage may still read historical ledger. Set automatically when switching effort to ECO (unless you pass --lucille=YES in the same call)

Re-enable anytime with php artisan larapilot:settings-set --lucille=YES without leaving ECO. Details: Usage & Lucille.

Decision journal & regression guard on by default

settings.decision_log (default YES) keeps an append-only, timestamped log of every explicit user choice — fixed-choice answers and free-text directives like “the background must be orange” — in .larapilot/decisions.yaml.

  • After a choice is settled a skill runs php artisan larapilot:decision-log --topic="…" --value="…" --source=askquestion|chat --skill=….
  • Before recording a value for a topic that already has a decision it runs php artisan larapilot:decision-check --topic="…" --value="…". When today’s value differs from an earlier non-superseded one the check returns that decision (with its date) so the skill can ask you to confirm — “on 2026-05-01 you chose orange; confirm red supersedes it” — then re-logs with --supersedes=<id>.
  • The file is never rewritten in place; a reversal is a new entry. Read-only larapilot:decision-check is also on the MCP allowlist.
  • Turn it off with php artisan larapilot:settings-set --decision-log=NO.
  • The dashboard renders the journal on the PRD page (project / discovery vs per-story groups, timeline) and on each spec detail page (entries for that story only).

Prior-art check on by default

settings.prior_art (default YES) adds one step to /larapilot-inception, after the goal is challenged and before scope is written: is there already a product, an open-source project, or a package that does this?

  • Consent first. A search query can expose a confidential idea. Sebastian shows the queries he would run and asks: Search now · Search with generic terms only · Skip — I know the alternatives · Skip — confidential.
  • Where he looks depends on the project kind: Packagist and GitHub for a package; GitHub, self-hosted catalogs, and alternatives directories for an application; Laravel CMS and commerce platforms for a site. A Personal project skips the step.
  • What you get — .larapilot/research/prior-art.md: up to five candidates with license, stack, last release, what each covers and lacks, and what adopting it would cost.
  • You decide: Build anyway, Adopt / fork, Integrate as dependency, or Not checked. The verdict is written in the PRD as **Prior Art:** and shown on the dashboard's Inception page.
  • When a mature product already does it and is not built on Laravel, inception says so and stops with the report.
  • With no web access in the editor, or with the setting off, the PRD says Not checked — never "nothing exists". Turn it off with php artisan larapilot:settings-set --prior-art=NO.

Code change history opt-in

settings.code_history (default NO) records, per spec/task, which files and line ranges were touched — read straight from the task’s git commit — into .larapilot/code-history.yaml.

  • Enable with php artisan larapilot:settings-set --code-history=YES; then larapilot-implement runs php artisan larapilot:code-log --spec=US-XXX --task=TASK-NN --skill=larapilot-implement after each task-done (--commit= / --range= override; falls back to the working-tree diff).
  • php artisan larapilot:code-history --file=app/Models/Post.php (read-only, also on the MCP allowlist) reports every touchpoint for a path.

Account opt-in

settings.account is NONE (default), FREELANCE, or COMPANY. Anything but NONE unlocks /larapilot-economics and the Economics page: FREELANCE prices with sole-trader regimes (Italian forfettario, IRPEF, autónomo, …), COMPANY with corporate tax plus dividend extraction (SRL, SPA, Ltd, GmbH, C-Corp, …). OFF is accepted as an alias of NONE.

unlock Economics
php artisan larapilot:settings-set --account=FREELANCE
php artisan larapilot:economics-set --country=IT --regime=forfettario_15 --hourly-rate=55

Release mode opt-in

settings.release_mode (default NO) turns on semver release tracking in .larapilot/releases.yaml, the /larapilot-release skill, release AskQuestion rounds in inception/adopt/feature, Gitflow release/x.y.z branches when git_mode is Gitflow, and the release ship ceremony in /larapilot-ship. Enable with php artisan larapilot:settings-set --release-mode=YES. Skill: /larapilot-release.

Project docs opt-in

settings.project_docs (default NO) obligates Albert to maintain the handbook in .larapilot/docs/handbook/ — bootstrap + incremental chapter updates after material product changes. Enable with php artisan larapilot:settings-set --project-docs=YES. Skill: /larapilot-project-docs.

Workflow hooks opt-in

settings.hooks (default NO) runs the commands and skills of .larapilot/hooks.yaml on the transitions of the loop: a before hook that fails blocks the transition, an after hook is reported. Enable with php artisan larapilot:settings-set --hooks=YES; LARAPILOT_HOOKS_ENABLED=false turns them off on one machine. Details: Workflow hooks.

Internal feedback / comments opt-in

settings.comments (default NO) gates the comment form on dashboard spec pages, POST /larapilot/api/specs/{code}/comments, and larapilot:spec-comment. Turn it on with php artisan larapilot:settings-set --comments=YES. Comments land in .larapilot/internal-feedback/{code}.md; a comment flagged [blocks-merge] stops spec-approve until it is resolved (or --force). The env kill-switch LARAPILOT_COMMENTS_ENABLED=false still disables writes even when the project setting is ON. Projects installed before v3.0.3 with an explicit comments: true keep it.

Dashboard auth opt-in

settings.dashboard_auth (default NO) puts HTTP Basic Auth in front of the dashboard UI and of the mockups at /mockups. It never gates /larapilot/api/* (that stays LARAPILOT_API_TOKEN) or the MCP server, and the dashboard is still never served in production. On a shared staging host, turn it on before anything else: with the setting OFF, the board, the PRD, the specs, the git history, and the security findings are open to whoever reaches the host.

  • Credentials are argon2id/bcrypt hashes in .larapilot/auth.yaml — no database, no User model, never cleartext. The file is added to .gitignore automatically the first time a user is written.
  • Manage users with php artisan larapilot:dashboard-user {list|add <username>|remove <username>} — add prompts for the password (or takes --password=).
  • Failed sign-ins are rate-limited per IP (LARAPILOT_DASHBOARD_AUTH_MAX_ATTEMPTS, default 30/min). With the setting ON and no users configured the dashboard fails closed with HTTP 503: every page shows a notice that the area is protected by this setting and gives the command that creates the first user. auth.yaml is git-ignored, so a fresh clone or a deploy starts on that notice.
  • The File manager, Database, and Logs pages go further: outside local/development/testing they answer 404 until this setting is YES.
  • Enable with php artisan larapilot:settings-set --dashboard-auth=YES. Serve over HTTPS on shared hosts — Basic Auth sends credentials on every request. Setup: Forges & notifications · .larapilot/integrations.md.

API auth opt-in

settings.api_auth (default NO) makes LARAPILOT_API_TOKEN mandatory on every /larapilot/api/* request — the JSON API only. It never gates the dashboard UI (dashboard_auth) or the MCP server, and the API is still never served in production.

  • With api_auth=NO (default): the token is enforced on every endpoint when it is set; when it is not, read endpoints stay open in the allowed environments and writes are refused outside local/development/testing.
  • With api_auth=YES: every request — reads and writes, including /diagnostics, /openapi.json, and /docs — must carry the token as Authorization: Bearer <token> or X-Larapilot-Token: <token>. With the setting ON and no LARAPILOT_API_TOKEN configured the API fails closed with HTTP 503 and says why — a JSON message that names the setting and tells how to add the token, or the same explanation as a page when a browser opens the API docs; a wrong or missing token is HTTP 401.
  • The token lives in the caller's environment, never in .larapilot/. Enable with php artisan larapilot:settings-set --api-auth=YES. Client setup + curl / Laravel HTTP / fetch / CI examples: .larapilot/integrations.md → API access.
  • The php artisan larapilot:diagnostics CLI and the MCP diagnostics tool are local and need no token — only the HTTP endpoint is gated.

Security scan opt-in

settings.security_scan (default NO) folds a static Laravel security scan into /larapilot-review and the pre-ship gate. It uses the optional dev package andreapollastri/checkpoint (php artisan checkpoint:scan — static checks plus composer/npm audit), run through php artisan larapilot:checkpoint-scan so the result lands on Security → Checkpoint. Larapilot never bundles the scanner; with the setting off it runs only when someone asks — the command, or Run the scan on the dashboard. See SBOM, CVE & Checkpoint.

turn it on
composer require --dev andreapollastri/checkpoint
php artisan larapilot:settings-set --security-scan=YES
  • FAIL findings are review blockers: fix them, or record a waiver with larapilot:decision-log. WARN findings become review notes.
  • If the package is missing, the review skill stops and asks you to install it.
  • Owned by Lars, alongside dashboard_auth and api_auth. The client quote only promises a security scan when this setting is YES. Setup: .larapilot/integrations.md → Security scan.

Aikido opt-in

settings.aikido (default NO) reads the findings of Aikido for the repository. Aikido scans on its side; Larapilot runs no scanner and only reads the result, with credentials kept in .env. While the setting is off, Aikido is never called.

turn it on
php artisan larapilot:settings-set --aikido=YES
php artisan larapilot:aikido-status

Owned by Lars. Everything else — the credentials, the skill, the gate — is in Aikido.

Production errors opt-in

settings.errors (default NO) reads the errors the running application throws from one tracker, and settings.errors_provider names it: boogle (the default), sentry, bugsnag, flare, datadog, rollbar, honeybadger, or cloudwatch. The credentials stay in .env. While errors is off no tracker is called, and the tracker that was named is kept for when it is turned on again. /larapilot-error asks which tracker when none is set, and turns the errors on.

turn it on
php artisan larapilot:settings-set --errors=YES --errors-provider=sentry
php artisan larapilot:errors-status

settings.boogle is the name the setting had when Boogle was the only tracker: --boogle=YES is --errors=YES --errors-provider=boogle, and the key is kept in step with the two. A project that turned Boogle on before 4.1.3 has nothing to change.

Owned by Sophia. The trackers, what each needs in .env, and the skill are in Production errors.

Remote forges opt-in

settings.github, gitlab, bitbucket, and azure (all default NO) are orthogonal to git_mode. Enable the forge matching origin:

  • GitHub — gh CLI; probe larapilot:github-status
  • GitLab — glab CLI (MR); probe larapilot:gitlab-status
  • Bitbucket Cloud — REST API with access token or app password; probe larapilot:bitbucket-status
  • Azure DevOps — az repos (azure-devops extension) or REST API with a PAT; probe larapilot:azure-status

When ON, skills open/update the PR/MR, always print the URL, and can notify pr_opened / pr_updated. Full setup: Forges & notifications · .larapilot/integrations.md.

Notifications opt-in

Master switch settings.notifications plus notify_slack / notify_discord / notify_telegram (all default NO). Secrets stay in .env. Fan-out via php artisan larapilot:notify. Hard hooks: task-done → task_done, spec-approve → spec_done. Full event list: Forges & notifications.

Environment paths

Machine-specific absolute paths must not appear in committed YAML or skill examples. Store them in .env and resolve via config-show:

Concern Env key Set via
External frontend repo LARAPILOT_FRONTEND_REPO_PATH larapilot:frontend-set --path=… or /larapilot-frontend-companion

When a skill needs a path that is missing from env, it keeps asking until you provide it, then persists with the matching CLI command. The PRD records the stack, not the absolute path.

Every flag, with its default

Terminal
php artisan larapilot:settings-set \
  --effort=STANDARD \
  --backlog=STANDARD \
  --git-mode=GITFLOW \
  --testing=NORMAL \
  --account=NONE \
  --auto-approve=NO \
  --lucille=YES \
  --decision-log=YES \
  --code-history=NO \
  --prior-art=YES \
  --release-mode=NO \
  --project-docs=NO \
  --hooks=NO \
  --comments=NO \
  --dashboard-auth=NO \
  --api-auth=NO \
  --security-scan=NO \
  --aikido=NO \
  --errors=NO \
  --errors-provider=boogle \
  --github=NO \
  --gitlab=NO \
  --bitbucket=NO \
  --azure=NO \
  --notifications=NO \
  --notify-slack=NO \
  --notify-discord=NO \
  --notify-telegram=NO

Personas

30 personas are lenses, not costumes. Each skill activates a subset of the squad — they speak in chat as 💎 Mark:, 📐 John:, etc., applying a specific kind of scrutiny to the work at hand. Zoey (AI Guru) is active in every skill; Lucille is active quietly by default (usage ledger). Nothing here is a separate agent with its own memory: a persona is the point of view the agent takes while it does one part of the work.

Discovery

Zoey sharpens user intent and flags session-budget risks. Lucille asks for delivery deadlines (skippable) and starts the ledger. Mark drives scope and MoSCoW. Jennifer and Benjamin frame market context. John and Aurora co-own architecture — John designs with SOLID and N+1-aware query shape; Aurora brings SaaS economics. Mike owns data architecture (SQL/NoSQL, tree patterns, search, migrations) when persistence is non-trivial. Sabrine leads legacy parity when legacy/ has content. Elise and Joe co-own the design system. Emma, Lauren join for public surfaces; Ricky for mobile apps; Albert scopes documentation; Sarah joins when CLI/Git/CI/Linux surfaces appear.

Planning

Zoey recommends sub-agent vs inline passes. Tom sharpens acceptance criteria. John designs the technical approach; Mike designs schema and query plans. Anne defines test strategy. Andrew enforces Laravel conventions. Albert plans baseline docs on every spec and extended deliverables when approved. Alex plans FE/BE integration with Andrew, Joe, and Jack. Sarah plans CLI, Git mechanics (conflicts/rebase), forge automation, CI pipeline scripts, and Linux/server shell tasks when those surfaces appear. Optional sub-agents explore the codebase in readonly mode. Autopilot does that mapping inside the spec worker, so the parent session does not keep the files.

Implementation

Alex writes SOLID, N+1-free code; Mike guides migrations and data access. Anne ships Pest tests and documents manual tests. Robert checks plan adherence, Gitflow, and the quality bar — involves Sabrine on refactoring/porting. Joe enforces design-system consistency. Lars runs OWASP-aligned security review. Under autopilot those two reviews start in the parent session, after the implement worker has returned. Sarah leads Git conflict/rebase resolution and implements pipeline YAML, forge automation, and server shell scripts. Ricky, Marika, Matt, and Albert join when mobile, copy, integrations, or docs are in scope. Lucille logs the session quietly at the end.

Review

Robert presents the human gate — including SOLID and N+1 checks — with Sabrine on refactoring/porting specs. Joe checks design-system compliance. Marika and Emily verify typos and translation consistency. Anne attaches automated evidence and manual test recommendations. Mike reviews schema/migration risk when data tasks shipped.

Ship & support

Jack owns CI/CD gates and deploy orchestration; Sarah owns pipeline scripts and server-side shell those runbooks invoke. Oliver red-teams before launch. Albert ships client manuals and API docs. Sophia routes bugs and runs the front door: with Mark and Tom she decides whether a request is a bug or a feature (/larapilot-triage). Violet and Emily cover compliance and localization. Lucille reports schedule drift vs deadlines via /larapilot-usage.

Output economy varies by phase: concise status lines during implement/review; full artifacts and code stay complete. See .larapilot/shared-runtime.md for brevity rules.

💎
Mark
Product Manager
🧭
Jennifer
Business Strategist
🏢
Benjamin
Business Consultant
💡
Sebastian
Innovator
🔎
Tom
Requirements Analyst
📐
John
Architect
🗄️
Mike
Database Expert
🔧
Alex
Full-Stack Developer
🧪
Anne
Test Architect
🛡️
Robert
Code Reviewer
🔐
Lars
Security Expert
🚀
Jack
DevOps Engineer
⌨️
Sarah
CLI, Git & Linux
📒
Lucille
Project Tracking
💰
Aurora
FinOps Expert
⚖️
Violet
Legal Expert
📈
Emma
SEO & Web Performance
💬
Lauren
Social Media Manager
🎨
Elise
UX Designer
✨
Joe
Frontend Expert
📱
Ricky
App Developer
📝
Albert
Tech Writer
🤖
Zoey
AI Guru
✍️
Marika
Copywriter
🔄
Sabrine
Legacy Porting & Migration
👾
Andrew
Laravel Expert
🔗
Matt
Integration Manager
🎯
Oliver
Ethical Hacker
🎧
Sophia
Support Manager
🌍
Emily
Translator

Workflow

Every user story moves through a strict state machine on a Gitflow branch model. Skills drive transitions; invalid jumps are blocked automatically. After inception and backlog creation, you repeat the per-story loop until the MVP is shipped.

Greenfield — repeat per story
/larapilot-inception "…" → /larapilot-spec → /larapilot-plan US-XXX → /larapilot-implement US-XXX → /larapilot-review US-XXX

Optional: /larapilot-design before plan · /larapilot-ship when all MVP stories are DONE · /larapilot-autopilot to batch plan+implement, one fresh context per spec · /larapilot-settings · /larapilot-usage · /larapilot-feature or /larapilot-bug on existing products (/larapilot-triage when you cannot tell which) · Package kind skips UI mockups unless the package ships UI.

Git workflow — Gitflow

Git behavior is gated by settings.git_mode (set via /larapilot-settings). Default is GITFLOW without automatic push. Choose GITFLOW_PUSH for push-after-each-task, or NO_GITFLOW to skip branch/PR ceremony. Jack proposes the workflow at inception; Sarah owns Git mechanics (conflicts, rebase), forge automation, and CI pipeline scripts; Robert enforces the active mode.

When two developers work the same project and a merge conflict lands — in source or in a .larapilot/ artifact like backlog.yaml — resolve code with git, but for .larapilot/ files take the other side and re-run the Larapilot command that produced your change rather than hand-editing the markers.

Branch Purpose
main Production-ready; tagged releases only
develop Integration branch for the next release
feature/US-XXX-short-desc One user story or cohesive feature; branch from develop
release/x.y.z Release prep — version bump, changelog, final QA; merge → main + back-merge → develop
hotfix/x.y.z Urgent production fix; branch from main; merge → main + develop

In Gitflow modes: no direct commits to main or develop; one atomic Conventional Commit per plan task. Push and remote PR updates run only when git_mode is GITFLOW_PUSH (or the user asks). Critical production defects route through hotfix/* via /larapilot-bug. Mode table: Git mode settings.

When release_mode=YES and Gitflow is active, specs assigned to an in_progress release branch from release/x.y.z and merge back into that branch (see /larapilot-release). Unassigned specs keep branching from develop. Multiple release/* branches may run in parallel — Sarah confirms which release context is active before starting work.

Status machine

Status Meaning Skill to advance
TODO Spec exists in backlog, not yet planned /larapilot-plan US-XXX
PLANNED Technical plan written and validated /larapilot-implement US-XXX
IN PROGRESS Implementation underway — tasks ticking off Skill completes tasks → auto REVIEW
REVIEW Code delivered, awaiting acceptance /larapilot-review US-XXX — or autopilot spec-approve when auto_approve: YES
DONE Approved — spec is shipped Next US-XXX or /larapilot-ship

Transition guards

The workflow engine rejects invalid state changes — for example, you cannot implement before planning, or approve before review. When you reject a review, the spec returns to TODO with your feedback attached so the agent can replan and reimplement.

Human-in-the-loop

Discovery interviews use AskQuestion wizards. By default, review is yours — the agent proposes, you approve or send back. No spec reaches DONE without explicit acceptance unless you set auto_approve: YES in project settings (opt-in for /larapilot-autopilot).

The PRD

The PRD is the product contract: what the product promises, to whom, and how well. Everything downstream is measured against it. A story exists because a requirement does, a bug is a promise that was not kept, and a review checks that it was. It is one Markdown file, .larapilot/docs/PRD.md, written by inception and changed only through skills.

What is in it

Section What it holds Who uses it later
Elevator Pitch · Vision The product in one paragraph, and what changes for its users if it works Everyone. The dashboard's functional summary opens with it
User Personas Who has the problem, their goals, and what they do today instead Stories are written as a persona
User Journeys J-001 One per way a persona gets value: trigger, steps, the state that means it worked, the ways it fails. The core journey comes first /larapilot-spec cuts one story per journey
Domain Model The nouns of the product: entities, their states, how they relate, and a glossary. Not a database schema Criteria, plans, and class names use these words and no synonym
Functional Requirements FR-001 One promise each: MoSCoW tag, journey, actor and trigger, behavior, Done means bullets a tester can verify, what is not included, what it depends on, where it came from Acceptance criteria are built from Done means. Triage and bug measure a request against them
Non-Functional Requirements NFR-001 Quality targets as a table: category, a measurable target, what it applies to, how it is verified Become criteria on the stories they apply to. Review and ship judge against them
MVP Scope The fixed choices — project kind, origin, delivery target, business model, prior-art verdict, success signal, deadlines — then In Scope, Out of Scope, Future Phases How much of the PRD becomes stories now. Economics prices from it
Risks & Assumptions Q-001 The riskiest assumption, the kill condition, assumptions and how each is validated, risks, open questions with an owner and a date, and everything left Not decided An open question blocks the story that depends on it instead of being guessed
Technical Architecture Every choice that was asked: who runs the server, support window, frontend topology, stack, data store, integrations, tenancy Plan, implement, and ship honor it instead of asking again
PRD Revision History One row per change made after inception: date, the skill that made it, what changed You, when you need to know why the PRD says what it says

The PRD is written in the language of your conversation. Ids and the MoSCoW labels stay as they are in every language. A complete example is in the New product walkthrough.

What validate-prd checks

Finding Severity Meaning
PRD_EMPTY · PRD_MISSING_SECTION Error One of the six required sections is missing: Elevator Pitch, Vision, User Personas, Functional Requirements, MVP Scope, Technical Architecture. Headings are recognized in English, Italian, Spanish, and French; any other language passes with six ## headings
PRD_RECOMMENDED_SECTION Warning User Journeys, Domain Model, Non-Functional Requirements, or Risks & Assumptions is missing — usually a PRD written by an older version
PRD_FR_MISSING_MOSCOW Warning A requirement has no **MoSCoW:** line, so spec cannot tell whether to build it
PRD_DUPLICATE_ID Warning The same id is defined twice
PRD_DANGLING_REFERENCE Warning An id is cited and never defined. Ids named in the revision history are not counted

An error fails the command. A warning never does, so an older PRD stays valid — and /larapilot-prd "Upgrade the PRD" clears the warnings.

How the PRD changes after inception

Three skills may change the PRD, and each one leaves a row in the revision history. Find what happened in the first column.

What happened Skill What changes in the PRD
You want one new capability /larapilot-feature A new FR-XXX, written in full
It works as agreed, and you now want it different — a change request /larapilot-feature The requirement is edited in place: same id, new behavior, before and after in the history
Something is broken, and the PRD or a story already promised it /larapilot-bug Nothing. The fix is a story; the report goes to support/intake.md
Something is broken, and nobody ever wrote the promise down — a requirement gap /larapilot-bug The missing sentence, where it belongs: a Done means bullet under the requirement, or an NFR row. Never a requirement called "Fix …"
Priorities, scope, wording, a target, or a recorded decision must change — nothing is new, nothing is broken /larapilot-prd The smallest edit that says it. See Revision kinds
You edited PRD.md by hand /larapilot-prd Your edit stays. The skill adds the history row, validates, and checks the backlog
A review was sent back · a refactor · performance work nobody will notice · a production hotfix /larapilot-review · /larapilot-plan · /larapilot-bug Nothing. These change how the product is built, not what it promises
The vision or the target user changes — a pivot /larapilot-inception Rewritten with the current PRD as input. Ids whose promise survives are kept
You cannot tell which of these it is /larapilot-triage Triage decides and runs the right skill

When in doubt, leave the PRD alone and write the story. A story can be corrected next week. A PRD that collects fixes stops being a contract.

PRD.md — revision history after three months
## PRD Revision History

| Date | Trigger | Summary |
| --- | --- | --- |
| 2026-07-01 | larapilot-inception | Initial PRD |
| 2026-07-15 | larapilot-feature US-011 | Added FR-011 Export PDF (MoSCoW: Should) |
| 2026-07-15 | larapilot-bug → FR-003 gap | SSO must work on Safari 17+ (macOS/iOS) |
| 2026-08-02 | larapilot-prd — Re-scope + Sharpen | FR-009 retired; FR-006 Could → Must; NFR-001 p95 < 300 ms |
| 2026-08-20 | larapilot-feature US-019 (change request) | FR-011: PDF opens in the browser instead of downloading |

Revision kinds

/larapilot-prd starts by naming the kind of change, because the kind decides how many questions it asks and how much of the backlog moves. One request can carry several.

Kind What changes Example Effect on the backlog
Editorial Wording only — no promise moves Typos, a clearer sentence, a glossary term None
Sharpen A promise becomes testable or complete Done means added to a requirement, a number for a quality target, an open question answered, a Not decided settled Criteria added to open stories
Re-scope What is in, what is out, and when A MoSCoW tag changes, something moves to Future Phases, the delivery target changes, a requirement is retired Stories created, deferred, or deleted
Re-model The nouns and the paths An entity is renamed, a state is added, a journey is split, a persona is added Stories that use the old names are rewritten
Re-decide A recorded decision Business model, who runs the server, support window, topology, data store, the prior-art verdict Architecture stories. The quote is recomputed
Upgrade The shape of the PRD, not its promises An older PRD gains journeys, domain model, quality targets, risks None
Pivot The vision, the target user, or the core journey A new audience Not a revision — it goes to /larapilot-inception

Whatever the kind, nothing is written before the readback: Mark shows before and after for each item, then asks Apply · Revise · Cancel. When a security, privacy, or accessibility target goes down, Lars or Violet states the consequence first. Walkthrough.

Ids are permanent

Stories, plans, commits, and developer docs cite FR-, J-, NFR-, and Q- ids. A revision never breaks those citations.

You want to What happens to the id
Drop a requirement It is retired: the heading stays, tagged Won't, with a **Retired:** line giving the date and the reason
Split a requirement in two Two new ids are created. The original stays with **Superseded by:** FR-012, FR-013
Merge two requirements One survives and cites **Merges:** FR-007. The other is retired
Rename an entity or a state It is renamed in every journey, requirement, and quality target in the same revision. The glossary keeps the old word as formerly
Reorder or renumber Never. A number is never reused either

The backlog follows

A changed promise is worth nothing if the stories still describe the old one. larapilot:prd-impact lists the stories that cite the ids being changed, and says what each one needs. What it needs depends on how far the story has gone.

The story is action What happens
TODO update_spec The story is rewritten. Same code, same status
PLANNED update_and_replan The story is rewritten, then planned again: its plan was written against the old promise
IN PROGRESS coordinate You are told work is under way. Nothing changes without your consent
REVIEW rework The story goes back to TODO with the change as feedback (spec-request-changes)
DONE new_spec Never reopened. A changed promise becomes a change request through /larapilot-feature; a promise the code never met becomes a bug
Terminal
php artisan larapilot:prd-impact --ids=FR-004,J-001   # the stories that cite these ids
php artisan larapilot:prd-impact                       # the whole PRD: every id and the stories that cite it

Without --ids the command is a coverage report. untraced lists the ids no story cites, and uncovered_must the Must requirements among them — the ones that would ship unbuilt. /larapilot-spec runs it after every backlog change.

Feature, bug, or revision — side by side

/larapilot-feature /larapilot-bug /larapilot-prd
You say "Add PDF export for invoices" "SSO login fails on Safari" "Drop the CSV export and say what fast means"
It asks Priority, journey, size, mockups, where it goes in the backlog Severity, environment, how to reproduce, which story it maps to Only what it cannot work out from the PRD
It writes One new story An intake entry and a fix story, or a rework of a story in review No story of its own. It rewrites the stories the change touches
The PRD Gains or changes one requirement Untouched, unless a requirement was never written down Is the point of the skill
Before saving A readback you confirm. Nothing is written before it
Then /larapilot-plan → /larapilot-implement → /larapilot-review on each story

Rule files: .larapilot/runtime-ops-1.md (PRD Living Document) and runtime-ops-3.md (PRD Revision).

Artifacts

Everything the workflow produces lives in .larapilot/ inside your repo. Skills read and write these files; they survive across editor sessions and give every agent the same ground truth.

.larapilot/ ├── config.yaml # connector, paths, settings — committed ├── choices.yaml # inception answers snapshot (choices-set) ├── decisions.yaml # decision journal — append-only (decision_log) ├── code-history.yaml # files + line ranges per spec/task (code_history) ├── releases.yaml # semver release ledger (release_mode) ├── tracker.yaml # spec → tracker id map — identifiers only, commit it ├── economics.yaml # Economics profile (account ≠ NONE) ├── economics.snapshot.yaml # last computed quote — refreshes itself ├── economics.market.yaml # competitor research (Jennifer + Benjamin) ├── auth.yaml # dashboard users, hashed — git-ignored ├── shared-runtime.md # runtime index + Read protocol ├── runtime-*.md # runtime section files — refreshed on update ├── task-templates.md # plan/implement task shapes ├── integrations.md # setup guide: forges, chat, API, security scan ├── backlog.yaml # spec index + statuses ├── specs/US-XXX.yaml # story + acceptance criteria ├── plans/US-XXX-plan.yaml # tasks, deliverables, test strategy ├── docs/ │ ├── PRD.md # living product contract │ ├── quote.md # client quote, PRD language │ ├── devs/ # developer domain docs — always English │ ├── handbook/ # living handbook (project_docs=YES) │ ├── review/ # Robert / Lars findings │ ├── test-results/ # per-spec test evidence │ ├── security/ # OWASP assessments │ ├── launch/ # launch checks │ └── support/ # bug intake, triage notes ├── mockups/{spec}/ # HTML mockups; styles/{slug}/ for variants ├── internal-feedback/ # PM/dev comments per spec ├── usage/ # Lucille ledger.jsonl + schedule.yaml ├── skills/{name}/SKILL.md # your custom Boost skills ├── client-materials/ # drop client docs before inception ├── legacy/ # legacy snapshot for rewrite/port ├── research/ # prior art, codebase analysis, parity, reference products ├── brand/ # logo, favicon, OG image ├── design-systems/ # Filament · Starter Kit · Bootstrap 5 · Tailwind · AdminLTE └── techdocs/ # generated by backstage-export --write
Artifact Written by Purpose
config.yaml → settings /larapilot-settings effort, backlog, git_mode, testing, account, auto_approve, lucille, decision_log, code_history, release_mode, project_docs, comments, dashboard_auth, api_auth, security_scan, github, gitlab, bitbucket, azure, notifications, notify_* — see Settings
docs/devs/{domain}.md /larapilot-implement, /larapilot-adopt, /larapilot-bug, autopilot Why the code is built the way it is — one file per domain, always English, always current. See Developer domain docs
decisions.yaml larapilot:decision-log from any skill Append-only journal of your explicit choices + regression guard — see Decision journal
code-history.yaml larapilot:code-log after each task-done Files and line ranges touched per spec/task (code_history=YES)
tracker.yaml larapilot:tracker-push Spec → remote issue ids per provider. Commit it, or every machine creates duplicates
releases.yaml /larapilot-release, inception/adopt/feature/ship when release mode on Semver release ledger — versions, statuses, assigned specs, branches. CLI only; see /larapilot-release
docs/handbook/ /larapilot-project-docs, implement/review/ship when project docs on Living technical + functional handbook — see /larapilot-project-docs
skills/{name}/SKILL.md /larapilot-custom-skill User-authored Boost skills — see /larapilot-custom-skill
PRD.md /larapilot-inception, /larapilot-adopt; then /larapilot-feature, /larapilot-bug (requirement gap only), /larapilot-prd The product contract: personas, journeys, domain model, requirements, quality targets, scope, risks, architecture. See The PRD
backlog.yaml /larapilot-spec Ordered list of specs with status, priority, and traceability to FRs
specs/US-XXX.yaml /larapilot-spec, shortcuts User story body, acceptance criteria, epic link, priority
plans/US-XXX-plan.yaml /larapilot-plan Task breakdown with Git deliverables, test data, and dependencies
mockups/{spec}/ /larapilot-design Static HTML previews at /mockups/{spec} (dev/staging); gallery at /larapilot/design (presentation index + zip of HTML/assets); linked on spec detail and in the JSON API when present
internal-feedback/{code}.md Dashboard · larapilot:spec-comment Append-only PM/dev notes until the spec is DONE; blocking comments flagged with [blocks-merge]
design-systems/ larapilot:install · larapilot:update Packaged mockup references + optional custom folders — see design systems
usage/ skills (Lucille) · /larapilot-usage ledger.jsonl + schedule.yaml — tokens, minutes, deadlines; see Usage
economics.yaml · economics.snapshot.yaml · economics.market.yaml · docs/quote.md /larapilot-economics · economics-set · economics-market-write · economics-quote-write Account profile, the last computed quote, researched competitors, and the client quote — see Economics
choices.yaml inception · larapilot:choices-set Snapshot of the fixed choices made at inception — kind, target, business model, prior-art verdict, success signal, kill condition, operations, topology, stack — for the dashboard and Economics
integrations.md larapilot:install · larapilot:update Setup guide for optional forges + chat notifications
Laravel app code /larapilot-implement Models, controllers, views, migrations, Pest tests — outside .larapilot/

JSON responses from CLI commands use schema larapilot/v1 — skills parse these envelopes to confirm writes and status changes. Inspect artifacts visually via the dashboard or programmatically via the API.

Developer domain docs

The code says what; the PRD says what the client wanted. Neither says why it is built this way. .larapilot/docs/devs/ is where that reasoning survives the session that produced it: one Markdown file per domain, written for whoever maintains the code next. It is always on — there is no setting for it.

.larapilot/docs/devs/
README.md                 # index — one row per domain, last spec that touched it
TEMPLATE.md               # the skeleton every file follows
billing.md
user-authentication.md
webhook-ingestion.md

The skeleton

Section What it carries
Metadata table Status · Introduced by · Last updated by · Owner persona
Purpose What the domain is responsible for, in business terms, and what breaks for the user without it
Functional flow Runtime behavior step by step, from the entry point to the outcome — failure paths included
Technical design Models, services, actions, jobs, events, routes, commands, config keys, tests — and where they live
Architectural choices What was chosen, what was rejected, and why — so nobody undoes a constraint by accident
Key decisions & invariants The rules that must stay true, and what breaks when they don't. References decisions.yaml ids
Extension points & gotchas Where new behavior plugs in, and the traps
Related Specs, plans, review files, other domains

Rules that keep them useful

  • Always English, whatever language the PRD and the conversation use — the files address whoever inherits the codebase, not the client.
  • Updated in the same spec that changes the behavior, inside the task commit — code, tests and doc together. A task is not task-done while its doc describes the old code.
  • Never deferred. effort: ECO makes the prose terse; the file is still written.
  • One file per domain, not per class. An entity gets its own file only when it carries behavior beyond CRUD.
  • Rewrite, don't append. The folder is the current truth; history belongs to Git.

Who enforces it

Skill What it does with domain docs
/larapilot-plan Names, per task, the domain file it must leave current (## Domain Docs in the task template)
/larapilot-implement Writes or updates the file before each task commit. Robert flags a domain whose code moved without its doc as a High finding (stale dev doc)
/larapilot-review Blocks on a touched domain with no current file — under ECO too
/larapilot-ship Blocks the release checklist on a stale or missing doc for a shipped feature
/larapilot-bug Corrects the doc when a fix changes behavior
/larapilot-adopt Writes a file for every domain the codebase analysis found, so a brownfield project reaches its first spec already documented

A project with no docs is brought level on the first change

config-show (and doctor) report the state under data.dev_docs: path, documented, count, and the domains already written. When documented is false and the codebase already has domains, the first spec, fix, or hotfix inventories every existing domain, writes a file for each, commits the backfill on its own — docs(US-XXX): bring developer domain docs level — and only then runs its own work. No question asked, no partial pass, no ECO exemption. Where the original reasoning can't be recovered from git history, the PRD, decisions.yaml, or the plans, the file says <!-- TODO: verify --> instead of inventing a motive.

Not the same as docs/handbook/. The project handbook is an opt-in manual for the whole project, mixed technical and functional. docs/devs/ is engineering-only, mandatory, and the only place that records implementation rationale. Contract: .larapilot/runtime-dev-docs.md · path key paths.dev_docs.

Usage & Lucille

Lucille keeps a committed ledger of AI tokens and wall-clock time, plus delivery deadlines. Every skill may append an entry at session end; /larapilot-usage interrogates the data. Dashboard Usage is the token and hour ledger; Plan is the Gantt and the deadlines. Usage still offers the Markdown download.

Categories

analysis · planning · implementation · support · feature · review · ship · other.

Commands

Log · report · schedule
php artisan larapilot:usage-log --category=implementation --tokens=12000 --minutes=45 --skill=implement --spec=US-001
php artisan larapilot:usage-report --format=json --insights
php artisan larapilot:usage-report --format=md --output=.larapilot/usage/report.md --insights --from=2026-08-01 --to=2026-08-31
php artisan larapilot:schedule-set --deadline=2026-09-15 --label="MVP go-live" --status=on_track
php artisan larapilot:schedule-set --deadline=2026-07-10 --label="Beta" --release=0.2.0
php artisan larapilot:schedule-show --only=alerts,findings
php artisan larapilot:schedule-apply --file=.larapilot/tmp-payload-schedule.json --dry-run

Report filters: --category= · --user= · --skill= · --spec= · --from= · --to= · --limit= · --insights. Formats: json · md · human.

Estimate vs build

The Usage page opens with what the delivered specs took. For every spec that is DONE: the hours it was estimated at — the hours of its plan's tasks, its story points when it has no plan, before the PM/QA buffer: the same estimate Economics prices — the time it took to build, how long it then waited in review, and the tokens the ledger holds for it. A spec that review sent back, or that was started over, says so. Above the table: the estimated hours, the build time, and how many times the first holds the second — given from three specs with a build time, since one spec proves nothing.

Estimate vs build on the Usage page
On Fernway: the project, then each delivered story with its estimate, build time, ratio, wait in review, and tokens.

The times are not logged by anyone: spec-start, spec-review, and spec-approve already write the moment of every step in the backlog, and the page reads them. So they cost no tokens, and they are there with Lucille off. What they are is worth keeping in mind. Build is the time the spec was IN PROGRESS, pauses included: a session left open overnight counts the night. In review is a wait, not the hours someone spent reviewing. And the estimate is the agent's own. The figure says how the plans compare with the builds; it does not say what a person would have taken. Only the tokens come from the ledger, which is why Lucille logs with --spec= whenever a session works one spec.

The same figures: a section of report.md, insights.actuals in usage-report --insights, build in GET /larapilot/api/metrics, and one line on Economics — for you, never in the client quote.

When Lucille is OFF

usage-log refuses writes. /larapilot-usage states she is excluded and may still run a read-only report on historical data. Re-enable: php artisan larapilot:settings-set --lucille=YES. ECO switches set lucille: NO automatically unless you pass --lucille=YES together with --effort=ECO.

Dashboard: /larapilot/usage for tokens and estimate vs build · /larapilot/plan for the Gantt and deadlines · download /larapilot/usage/report.md (dev/staging only). MCP RunArtisanTool allows larapilot:usage-report.

Laravel, PHP & DB upgrades

Three skills move the project to a new version: /larapilot-laravel-upgrade, /larapilot-php-upgrade, and /larapilot-db-upgrade. All three start with a readiness report and its criticalities — blocker, high, medium, low, info — and change nothing until you choose upgrade now, add it to the backlog, or stop at the report.

readiness
php artisan larapilot:stack                                   # versions, support windows, packages, pins
php artisan larapilot:upgrade-check --laravel=13 --report
php artisan larapilot:upgrade-check --php=8.4 --php-from=8.2
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

What the check reads

  • Dependencies — composer.lock, and Packagist for the releases of each direct dependency that support the target Laravel on the PHP that Laravel needs. It follows self.version into the packages of the same vendor, so Filament is measured by filament/support. Verdicts: ok, update (fits the constraint), bump (a new constraint, often a major), blocker, abandoned, private (Nova, Spark, a Satis — Packagist cannot see it). Answers are cached for a day.
  • PHP — the PHP the project is on (config.platform.php, else the floor of require.php), every installed package whose PHP requirement excludes the target with what requires it, code the target deprecates (implicitly nullable parameters on 8.4, non-canonical casts on 8.5, …), extensions that left core.
  • Database — raw SQL, column types, reserved words, and server options the target does not accept, with file and line (MySQL functions on the way to PostgreSQL, GROUP BY … DESC on MySQL 8.0, default-authentication-plugin on 8.4, …), and the checklist no scan can see.
  • Pins — Dockerfile and compose images, CI matrices, vapor.yml, .php-version, .nvmrc, phpstan.neon, rector.php.

Composer has the last word: the report names the --dry-run that asks it. The reports go to .larapilot/docs/upgrades/ (paths.upgrades).

Upgrade now

  1. A clean tree, a branch by git_mode, and a baseline: tests, Pint, Larastan, the build.
  2. PHP first when the target Laravel needs it; one Laravel major at a time; the database last, once the code runs on the target engine locally.
  3. Each step: the Composer change, the upgrade guide of that version (Boost Search Docs, never from memory), the package playbooks, Rector when the project has it, the gates, one commit.
  4. An upgrade report: criticalities resolved, followed up, or accepted; dependencies before and after; the deploy runbook; the rollback.

/larapilot-db-upgrade never connects to a production or shared database: it rehearses on a scratch database and writes the data move (pgloader with sequences reset, counts to compare) and the cutover. The About page of the dashboard shows each version with its support window and the command that checks the next upgrade.

The About page: Laravel, PHP, and database versions with their support windows
The About page of the dashboard: each version against its support window.

Forges & notifications

Optional remote forges and chat fan-out — all OFF by default, orthogonal to git_mode. Full install steps live in .larapilot/integrations.md (published on install/update). Toggle via /larapilot-settings or larapilot:settings-set.

Remote forges

Setting Tooling Probe
github gh CLI larapilot:github-status
gitlab glab CLI (MR) larapilot:gitlab-status
bitbucket Bitbucket Cloud REST (access token or app password) larapilot:bitbucket-status
azure Azure CLI (az repos + azure-devops ext) or REST (PAT) larapilot:azure-status

When ON, skills open/update the PR/MR (still respecting git_mode), always print the URL, and may emit pr_opened / pr_updated notifications. Secrets stay in .env — never in config.yaml.

Chat notifications

Master switch settings.notifications plus notify_slack / notify_discord / notify_telegram. Fan-out: php artisan larapilot:notify.

  • Hard hooks: task-done → task_done, spec-approve → spec_done
  • Skill events: PR/MR opened/updated, review, ship, schedule drift
  • Env: LARAPILOT_SLACK_WEBHOOK_URL, LARAPILOT_DISCORD_WEBHOOK_URL, LARAPILOT_TELEGRAM_BOT_TOKEN, LARAPILOT_TELEGRAM_CHAT_ID; Bitbucket BITBUCKET_ACCESS_TOKEN or username + app password; Azure DevOps AZURE_DEVOPS_EXT_PAT (or az login)

Settings tables: Remote forges · Notifications.

Dashboard access

Optional HTTP Basic Auth on the dashboard UI, via the dashboard_auth setting (default NO). UI-only — never /larapilot/api/* or MCP.

manage dashboard users
php artisan larapilot:dashboard-user add andrea        # prompts for the password (or --password=)
php artisan larapilot:dashboard-user list
php artisan larapilot:dashboard-user remove andrea
php artisan larapilot:settings-set --dashboard-auth=YES
  • Credentials are argon2id/bcrypt hashes in .larapilot/auth.yaml — no database, no User model; the file is added to .gitignore automatically.
  • Env: LARAPILOT_DASHBOARD_AUTH_REALM, LARAPILOT_DASHBOARD_AUTH_MAX_ATTEMPTS (failed sign-ins per minute per IP, default 30; 0 disables throttling).
  • dashboard_auth=YES with no users → dashboard fails closed (HTTP 503) on a notice that tells how to add the first one. Serve over HTTPS on shared hosts.

Settings table: Dashboard auth.

Security scan

Optional static scan with andreapollastri/checkpoint in /larapilot-review and before ship — FAIL blocks, WARN becomes a note. Off until you install the package and set security_scan=YES. Details: Project settings → Security scan.

Backstage portal

Larapilot is repo-level; Backstage is org-level. The integration publishes .larapilot/ into the developer portal — a catalog entity, a TechDocs site built from the PRD and backlog, and a live delivery snapshot. The direction is one-way: the workspace stays the source of truth, and workflow state never changes from the portal.

When it is worth it

Situation Recommendation
No Backstage in the organization Skip it — the dashboard already covers a single repo
Backstage exists, one Laravel repo Catalog entity + TechDocs — the PRD and backlog become discoverable next to every other service
Backstage with several Larapilot repos The real payoff — an entity provider polling /backstage gives one org-wide view of delivery state

Generate

Editor
/larapilot-backstage

The skill asks for owner, system, and lifecycle, persists them to .env, then generates. Equivalent CLI:

Terminal
php artisan larapilot:backstage-export           # preview the bundle — writes nothing
php artisan larapilot:backstage-export --write   # generate catalog + TechDocs
php artisan larapilot:backstage-export --write --force        # overwrite existing files
php artisan larapilot:backstage-export --write --no-techdocs  # catalog entity only
Generated Contents Overwritten?
catalog-info.yaml (repo root) Component entity, plus one API entity per OpenAPI contract found (storage/api-docs/api-docs.json, openapi.json, docs/openapi.json, .larapilot/openapi-product.json) Only with --force
mkdocs.yml (repo root) TechDocs config — docs_dir: .larapilot/techdocs, plugin techdocs-core, nav over PRD and backlog Only with --force
.larapilot/techdocs/ index.md (delivery snapshot), prd.md, backlog/index.md, backlog/US-XXX.md (spec body, plan, task checklist) Always — pages for deleted specs are pruned

The two root files may already belong to the project (an existing catalog entry, an existing MkDocs site), so Larapilot keeps them unless you pass --force; the envelope's data.hint names what was kept. Everything under .larapilot/techdocs/ is generated output — fix the PRD or the spec and regenerate rather than editing a page.

Catalog entity

catalog-info.yaml
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
  name: acme-shop
  namespace: default
  title: 'Acme Shop'
  description: 'A thing.'          # PRD elevator pitch, when present
  annotations:
    backstage.io/techdocs-ref: 'dir:.'
    larapilot.io/version: 2.2.0
    larapilot.io/workspace: .larapilot
    larapilot.io/prd: .larapilot/docs/PRD.md
    larapilot.io/board-url: 'https://staging.acme.test/larapilot'
    larapilot.io/api-url: 'https://staging.acme.test/larapilot/api'
  tags: [laravel, larapilot]
spec:
  type: service
  lifecycle: production
  owner: 'group:default/platform'
  system: commerce
  providesApis:
    - acme-shop-api
---
apiVersion: backstage.io/v1alpha1
kind: API
metadata:
  name: acme-shop-api
  title: 'Acme Shop API'
spec:
  type: openapi
  lifecycle: production
  owner: 'group:default/platform'
  definition:
    $text: ./openapi.json          # resolved by Backstage relative to this file

The name defaults to a slug of app.name, the description to the PRD Elevator Pitch (falling back to the composer description), and the larapilot.io/* annotations let a plugin locate the board and API for this entity.

Catalog identity

Identity describes the org catalog, not the delivery workflow — so it lives in Laravel config and .env, not in .larapilot/config.yaml. Never set it with larapilot:settings-set.

Env var Default Purpose
LARAPILOT_BACKSTAGE_ENABLED true Master switch — false also hides the API endpoints
LARAPILOT_BACKSTAGE_OWNER guests Set this. Backstage Group/User that owns the entity (platform or group:default/payments); unresolvable owners show as dangling refs
LARAPILOT_BACKSTAGE_SYSTEM — Parent System entity, when your org models them
LARAPILOT_BACKSTAGE_LIFECYCLE experimental experimental · production · deprecated
LARAPILOT_BACKSTAGE_COMPONENT_TYPE service service · website · library
LARAPILOT_BACKSTAGE_NAME slug of app.name Entity name override (also _TITLE, _DESCRIPTION, _NAMESPACE)
LARAPILOT_BACKSTAGE_BASE_URL app.url Base URL for catalog links and annotations — non-production only
LARAPILOT_BACKSTAGE_TECHDOCS true Generate the TechDocs site alongside the catalog entity
LARAPILOT_BACKSTAGE_WORKFLOW_API false Also register the dev-only Larapilot API as its own API entity — tooling, not a product contract

php artisan larapilot:config-show reports the resolved mapping under data.backstage (entity ref, owner, system, lifecycle, TechDocs paths, whether the catalog file exists).

TechDocs

MkDocs config and sources are generated together, so Backstage builds the docs site straight from the repository:

mkdocs.yml
site_name: 'Acme Shop'
site_description: 'A thing.'
docs_dir: .larapilot/techdocs
plugins:
  - techdocs-core
nav:
  - Overview: index.md
  - 'Product Requirements': prd.md
  - Backlog:
      - Overview: backlog/index.md
      - 'US-001 — Login': backlog/US-001.md

Specs and plans are YAML on disk, so each story page is rendered to Markdown: status/priority/points/epic table, task progress, internal-feedback counts, then the spec body, the technical plan, and the task checklist. Internal feedback bodies stay in the repo — only counts are published.

Live delivery data

For a Backstage frontend plugin or entity provider, two endpoints share the API gate (dev/staging only):

Endpoint Use
GET /larapilot/api/backstage Entities, rendered YAML, TechDocs metadata, and snapshot — metrics, per-status counts, blocking feedback, and a story list without spec bodies or plan text, so a portal can poll many repos cheaply
GET /larapilot/api/backstage/catalog-info.yaml The same entities as a YAML descriptor, consumable as a Backstage url location
GET /larapilot/api/backstage → data.snapshot (excerpt)
{
  "entity_ref": "component:default/acme-shop",
  "metrics": { "total": 12, "done": 5, "completion_rate": 41.7, "total_tasks": 61, "done_tasks": 40 },
  "counts_by_status": { "TODO": 5, "PLANNED": 2, "IN PROGRESS": 1, "REVIEW": 1, "DONE": 3 },
  "blocking_feedback": { "count": 1, "specs": ["US-003"] },
  "stories": [
    {
      "code": "US-001", "title": "Login", "status": "PLANNED", "priority": "HIGH", "points": 3,
      "tasks": { "total": 2, "done": 0 }, "blocking_feedback": 0,
      "techdocs_path": "backlog/US-001.md"
    }
  ],
  "links": { "board": "https://staging.acme.test/larapilot", "api": "https://staging.acme.test/larapilot/api" }
}
Proxy the API, never expose it. Call these endpoints through the Backstage backend proxy so LARAPILOT_API_TOKEN stays server-side — never from browser code, and never against a production host (the API returns 404 there by design). If the portal cannot reach a dev/staging environment, ship the committed catalog-info.yaml and TechDocs instead of the live endpoints.

Register & keep fresh

  • Commit catalog-info.yaml, mkdocs.yml, and .larapilot/techdocs/ on the default branch.
  • In Backstage: Create → Register existing component → paste the repo URL of catalog-info.yaml. Orgs with catalog discovery configured skip this step.
  • TechDocs builds from mkdocs.yml; the backstage.io/techdocs-ref: dir:. annotation is already set.
  • Regenerate after PRD, backlog, or plan changes — a CI job on the default branch running --write --force and committing the diff keeps the portal from drifting.

Personas & boundaries

  • Matt — owns the catalog mapping: owner, system, lifecycle, which APIs get registered
  • Jack — CI regeneration and environment reachability
  • Albert — TechDocs nav and readability
  • Lars — token/proxy boundary; the portal is never pointed at production
The portal renders; it does not drive. Backstage is a read surface over .larapilot/. Scope changes still go through /larapilot-feature and /larapilot-prd, and status still moves only through skills and Artisan.

Project trackers

Optional, API-key based sync between the backlog and the tool the rest of the organisation already lives in — Linear, Asana, Jira, Trello, ClickUp, or Monday. A PM or a client follows delivery without ever opening backlog.yaml. The workspace stays the source of truth; the tracker is a window, not a second workflow.

When it is worth it

Situation Recommendation
Solo dev, no external stakeholders Skip it — the dashboard at /larapilot already shows the board
Client or PM tracks work in their own tool Push the backlog so they see progress where they already look
Team runs sprints in Jira/Linear alongside the code Push after planning, pull before standup to catch drift
You want the tracker to drive the workflow Not supported by design — status moves through skills and Artisan

Setup

/larapilot-tracker

The skill picks the provider, tells you where to generate the API key, writes it to .env, validates the status map against the real board, dry-runs, then pushes. By hand:

php artisan larapilot:tracker-status --ping   # credentials + target board
php artisan larapilot:tracker-push --dry-run  # what would change, no API calls
php artisan larapilot:tracker-push            # backlog → tracker
php artisan larapilot:tracker-pull            # tracker → drift report (read-only)
php artisan larapilot:tracker-pull --apply    # write mapped statuses back

What gets mirrored

Provider Auth Destination Plan tasks become Status maps to
Linear personal API key team key (ENG) sub-issues workflow state
Jira (Cloud, REST v2) email + API token project key subtasks status, via a workflow transition
Asana personal access token project gid subtasks section (DONE also marks complete)
Trello key + token board id checklist items list (board column)
ClickUp personal token pk_… list id subtasks list status
Monday API token board id subitems status-column label

A user story becomes an issue titled US-XXX — Title carrying the spec body, priority, points, and epic. Plan tasks are mirrored as native subtasks, not a checklist buried in the description. One provider is active at a time, but links are stored per provider — switching tools, or switching back, never loses the mapping.

Push writes, pull reports

The direction is deliberately asymmetric. Push is authoritative: .larapilot/ decides what a story says and which column it sits in, and unchanged stories are skipped without an API call. Pull is a report — it lists drift and changes nothing until you pass --apply.

  • DONE is never applied from a tracker. DONE is a human review gate that records the merge commit — it stays with /larapilot-review and larapilot:spec-approve.
  • Spec text is never read back. Titles, bodies, and acceptance criteria are owned by .larapilot/; the card description says so, and edits made in the tracker are overwritten on the next push.
  • TODO and PLANNED sharing one column is normal and is not drift — a story is in sync when its forward mapping matches the remote label.
  • A remote status outside the map is reported as drift with no suggestion, never guessed at.

Set LARAPILOT_TRACKER_PULL_COMMENTS=true to import tracker comments as internal feedback — non-blocking, and imported once each.

Configuration

Env var Default Purpose
LARAPILOT_TRACKER_ENABLED false Master switch for the integration
LARAPILOT_TRACKER_PROVIDER — linear · asana · jira · trello · clickup · monday
LARAPILOT_TRACKER_SYNC_TASKS true Mirror plan tasks as native subtasks
LARAPILOT_TRACKER_PULL_COMMENTS false Import remote comments as internal feedback
LARAPILOT_MONDAY_DESCRIPTION_COLUMN — Monday items have no description field — point this at a long-text column

Per-provider credentials follow the same shape: LARAPILOT_LINEAR_API_KEY / _TEAM, LARAPILOT_JIRA_BASE_URL / _EMAIL / _API_TOKEN / _PROJECT, LARAPILOT_ASANA_TOKEN / _PROJECT, LARAPILOT_TRELLO_KEY / _TOKEN / _BOARD, LARAPILOT_CLICKUP_TOKEN / _LIST, LARAPILOT_MONDAY_TOKEN / _BOARD. Status maps live in config/larapilot.php → tracker.providers.{provider}.status_map. If a mapped column does not exist, the push fails and names the columns that do — Larapilot never creates columns in your tracker.

Personas & boundaries

  • Matt — provider choice, status mapping, link hygiene
  • Mark — what non-developers should see on the board
  • Jack — the CI push step on the default branch
  • Lars — the credential boundary: API keys live in .env, never in .larapilot/
Credentials never enter the repo. The tracker key is write-capable on a third-party workspace — a tighter boundary than the read-only Larapilot API. It belongs in .env and CI secrets. config-show and tracker-status report whether a credential is present, never its value. .larapilot/tracker.yaml is committed on purpose — it holds the spec → remote-id map so the team shares one mapping instead of each machine creating duplicate cards, and it contains identifiers only.

Aikido

Optional. Aikido scans the repository on its side — dependencies, code, leaked secrets, infrastructure, licenses — through its connection to the git provider. Larapilot runs no scanner and installs nothing: it reads what Aikido found over the public REST API, says what was already decided about each finding, and brings the rest into the workflow through /larapilot-triage.

Setup

  1. Connect the repository in Aikido, through the git provider (GitHub, GitLab, Bitbucket, Azure DevOps). Larapilot cannot do this step.
  2. Create the credentials in Aikido under Settings → Integrations → Public REST API. Reading needs the issues:read and repositories:read scopes; telling Aikido a decision needs issues:write; asking for a scan needs repositories:write.
  3. Put them in .env — never in .larapilot/, which is committed.
  4. Turn the setting on and check the connection.
.env
LARAPILOT_AIKIDO_CLIENT_ID=
LARAPILOT_AIKIDO_CLIENT_SECRET=
LARAPILOT_AIKIDO_REGION=eu        # eu (default) · us · au · me
LARAPILOT_AIKIDO_REPOSITORY=      # id or name in Aikido, for this machine; empty = chosen or found from the git remote
LARAPILOT_AIKIDO_FAIL_ON=high     # critical · high · medium · low · none
LARAPILOT_AIKIDO_PUSH_DECISIONS=true  # false = decisions stay in the project
LARAPILOT_AIKIDO_CACHE=300        # seconds the dashboard keeps what it read
LARAPILOT_AIKIDO_BASE_URL=        # only when the workspace is reached through another address
Terminal
php artisan larapilot:settings-set --aikido=YES
php artisan larapilot:aikido-status

aikido-status answers with enabled, configured, authenticated, the repository it matched (id, name, branch, last scan), and hints: one line for each thing that is missing, with what to do about it.

The repository

The repository is found from the git remote, by its address or by its name. When the code is scanned under another repository — a fork, a mirror, a monorepo, a name that differs — nothing matches: aikido-status answers needs_repository: true, and you are asked which one it is. /larapilot-aikido asks in chat, /larapilot/security shows the list with a form, and the terminal has a command. Nothing is chosen for you: a name that looks alike is not a match.

Terminal
php artisan larapilot:aikido-repos                # the repositories of the workspace
php artisan larapilot:aikido-repos --search=shop  # only the names that hold these letters
php artisan larapilot:aikido-repos --use=12       # this project is repository 12 (or its exact name)
php artisan larapilot:aikido-repos --forget       # back to the git remote

The choice is kept in .larapilot/aikido.yaml, which is committed, so every machine reads the same repository. LARAPILOT_AIKIDO_REPOSITORY in .env names it for one machine, and wins there.

The skill

in the editor
/larapilot-aikido
  1. Status — one line: repository, branch, last scan, the severity the gate fails on. With something missing, the hints are printed and the skill stops.
  2. Download — aikido-issues --new --report. The findings nobody decided about are shown in a table, the most severe first, 15 rows at most; the detail of all of them goes to .larapilot/docs/security/aikido.md.
  3. Scope — one question: the findings that stop the ship gate, every new finding, the ones you name, or none.
  4. Confirm — finding by finding (or in batches when there are many): resolve, waive with a reason, or skip for now. Waives are recorded immediately with aikido-link.
  5. Plan — aikido-plan --ids=… groups the confirmed ids by kind and fix; you approve the groups. Leaked secrets never merge with anything else.
  6. Hand off to triage — each group goes to /larapilot-triage in the same turn, with the block below. Triage measures it against the PRD like any request: a known vulnerability in shipped code is a bug when a requirement names security for that area, a bug with a requirement gap otherwise, and the verdict is not asked. /larapilot-bug then writes the fix spec, with a criterion that Aikido no longer reports the finding.
  7. Record — aikido-link 24 --spec=US-012, so the finding is not handed over again, on this machine or another.
what triage receives
Aikido finding
ids: 24, 25
severity: critical (95/100)
kind: Vulnerable dependency
title: guzzlehttp/psr7
where: fjord-invoices
cves: CVE-2026-1111
fix: Upgrade guzzlehttp/psr7 to 2.7.0 or later

What a finding can be

State Meaning At the ship gate
new Open in Aikido, and nobody decided about it Counts
in_backlog A spec of the backlog fixes it. Not fixed yet Counts, until Aikido no longer reports it
waived Accepted as it is, with a reason of at least a sentence, given by the user Does not count
no longer open Decided about here, and Aikido reports it as open no more: fixed, or waived here and ignored there Does not count

Decisions are told to Aikido

What you decide here is sent to Aikido, so the workspace says the same as the project.

Decision In Aikido
aikido-link 40 --waive --reason="…" The finding is ignored, with the reason as its comment. It leaves the open findings at once
aikido-link 24 --spec=US-012 A note on the finding: the spec that fixes it. The finding stays open
aikido-link 40 --forget The waiver is taken back: the finding is open again
  • A finding can be in several repositories of the workspace. When it is, only the issues of this repository are ignored, and the other projects keep theirs.
  • The credentials need the issues:write scope. When Aikido refuses, or cannot be reached, the decision is kept and listed as unsent; larapilot:aikido-push tells it later. It also tells the decisions taken with an earlier version.
  • A waiver Aikido would not take back is not forgotten here, so the two never disagree.
  • --local on aikido-link keeps one decision in the project; LARAPILOT_AIKIDO_PUSH_DECISIONS=false keeps them all.

The register for the client

larapilot:aikido-register writes {paths.security}/aikido-register.md, and Register for the client (.md) on /larapilot/security downloads it: one document with every finding of the repository — open with the fix that is planned, resolved with the date, ignored with the date and the reason — and a count by severity. It is what a client or an auditor asks for when a certification requires that every vulnerability is tracked to a decision, and it is written in the language of the PRD.

The reason of a finding waived here is the one you wrote. Aikido does not give back the reason of a finding ignored there by hand: the register lists it, and says the reason is kept in Aikido.

The verdict of the gate is FAIL when a finding at LARAPILOT_AIKIDO_FAIL_ON (default high) or above counts, WARN when only lower ones do, PASS otherwise. /larapilot-ship runs larapilot:aikido-issues --gate --report: FAIL is a release blocker, WARN a note in the assessment. The same command exits 1 on FAIL, so a pipeline can use it.

Commands

Command What it does
larapilot:aikido-status Setting, credentials, repository, last scan, hints. Read-only
larapilot:aikido-issues The open findings with their state and the verdict of the gate. --new only the ones with no decision · --severity=high that severity and above · --type=open_source one kind · --limit= · --report writes {paths.security}/aikido.md · --gate exits 1 on FAIL
larapilot:aikido-plan --ids=24,31 Groups confirmed ids by kind and fix for triage handoffs. Read-only
larapilot:aikido-link 24,25 --spec=US-012 The spec that fixes the findings, and a note about it in Aikido. A number Aikido does not report is refused
larapilot:aikido-link 40 --waive --reason="…" Accepts a finding as it is, keeps why, and ignores it in Aikido with that reason
larapilot:aikido-link 40 --forget Drops what was decided, and takes a waiver back in Aikido: the finding is new again. --local on any of the three tells Aikido nothing
larapilot:aikido-push Tells Aikido the decisions it was not told yet
larapilot:aikido-repos The repositories of the workspace. --search= narrows the list · --use=12 keeps the one this project is · --forget drops the choice
larapilot:aikido-register Writes the register for the client to {paths.security}/aikido-register.md
larapilot:aikido-scan Asks Aikido to scan the repository again. The scan runs there and takes minutes

aikido-status, aikido-issues, aikido-plan, and aikido-repos (the list only) are allowed through the MCP RunArtisanTool; the commands and the options that write are not.

On the dashboard

/larapilot/security is always in the menu. With the setting off it is two lines: what Aikido scans, and how to turn the link on. With the setting on it opens on the verdict of the gate, in a sentence — A release is stopped: 3 open findings are high or above, 2 with no decision yet — then the open findings by severity, then every finding, the most severe first. A row opens on what the finding is, how to fix it, where it is, its CVE, when it was first seen, and what was decided: the spec it is in the backlog as, with its status, or the reason it was waived. Findings filter by decision and by text. What was read is kept for five minutes; Read again asks Aikido anew, Download report (.md) saves the same document the skill writes, and Register for the client (.md) saves every finding — open, resolved, ignored with its reason. A decision Aikido was not told yet is said under the list. When the git remote matches no repository of Aikido, the page lists the repositories of the workspace and asks which one the project is. When Aikido cannot be read the page says what to check, and the rest of the dashboard is untouched.

What is kept, and where

What Where Committed
Client id and secret .env Never
Access token The cache, for as long as Aikido says it lasts, less a minute Never written to a file
Decisions: finding id, spec, reason of a waiver, whether Aikido was told. The repository that was chosen .larapilot/aikido.yaml Yes — the team shares one set of decisions
The register for the client: every finding, open, resolved, or ignored .larapilot/docs/security/aikido-register.md Your call: it names vulnerabilities that are still open
The report: every open finding with its decision .larapilot/docs/security/aikido.md Your call: it names vulnerabilities that are still open
What Larapilot never does. It never marks a finding as fixed: a finding leaves the list when Aikido no longer reports it, after the fix is merged and scanned. It never waives a finding: only the user does, with a reason — and only then is Aikido told to ignore it. It never chooses the repository: when the git remote finds none, you name it. It never rates a finding again: the severity is Aikido's. The address of the token endpoint is taken from the region (https://app.{region}.aikido.dev/api/oauth/token, and https://app.aikido.dev for eu).

Production errors

Optional. The running application throws, one tracker records it, and Larapilot reads the open errors back: it puts them together into bugs, says what was already decided about each, and brings the rest into the workflow through /larapilot-triage. settings.errors turns it on and settings.errors_provider names the tracker: /larapilot-error asks which one when none is set. The skill (/larapilot-error) and the commands (larapilot:errors-*) are the same for every tracker; the names the commands had when Boogle was the only one (larapilot:boogle-*) still answer, and the ledger keeps its name, .larapilot/boogle.yaml.

The trackers

Provider What is read In .env Closed from here
boogle — the default Every throw the self-hosted Boogle recorded, and the outages its uptime monitor found 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, over the Data Access API 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 for the service — or its 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). SENTRY_AUTH_TOKEN, SENTRY_ORG, SENTRY_PROJECT, DD_API_KEY, DD_APP_KEY, and DD_SITE are taken when the project has them already. One tracker is read at a time. Laravel Nightwatch is not among them: it publishes no API to read exceptions with.

  • One row is a throw — Boogle, CloudWatch, the logs of Datadog. Larapilot puts together the rows that share the exception, the file, and the line, counts them, and draws them day by day on the dashboard.
  • One row is a bug — Sentry, Bugsnag, Flare, Rollbar, Honeybadger, Datadog Error Tracking. The tracker grouped already: its grouping and its count are kept. The dashboard has no day chart, since such a tracker says when a bug was last thrown and not each time.

Setup with Boogle

  1. Have the application send its exceptions to Boogle: composer require andreapollastri/boogle-client, then php artisan boogle:install. Larapilot does not do this step.
  2. Create a token in Boogle, as an admin user, in the profile under API tokens. The admin API answers to admin users only.
  3. Put the address and the token in .env — never in .larapilot/, which is committed.
  4. Turn the setting on and check the connection.
.env
LARAPILOT_BOOGLE_URL=https://boogle.example.com   # empty = taken from BOOGLE_SERVER
LARAPILOT_BOOGLE_TOKEN=                            # the token of an admin user of Boogle
LARAPILOT_BOOGLE_PROJECT=                          # id or title in Boogle; empty = found by itself
LARAPILOT_BOOGLE_CACHE=300                         # seconds the dashboard keeps what it read
Terminal
php artisan larapilot:settings-set --errors=YES --errors-provider=boogle   # or --boogle=YES
php artisan larapilot:errors-status

errors-status answers with enabled, provider, configured, authenticated, the project it matched (id, title, address, group, whether the uptime is watched), remote_resolve, and hints: one line for each thing that is missing. The project is the one named in LARAPILOT_BOOGLE_PROJECT; with nothing named, the one whose key is BOOGLE_PROJECT_KEY — what the client package sends with — and then the one whose address is APP_URL.

Setup with another tracker

The application sends its exceptions to the tracker as it already does — Larapilot changes nothing there. Put what reads the tracker in .env, name the tracker, and check: with the errors on, errors-status asks the tracker, so a token it refuses is known before the skill runs.

.env — the variables of the tracker the project uses
# Sentry — an auth token with event:read (event:write to close an issue)
LARAPILOT_SENTRY_AUTH_TOKEN=             # or SENTRY_AUTH_TOKEN
LARAPILOT_SENTRY_ORGANIZATION=           # or SENTRY_ORG: the slug of the organization
LARAPILOT_SENTRY_PROJECT=                # or SENTRY_PROJECT: the slug of the project
LARAPILOT_SENTRY_URL=https://sentry.io   # self-hosted, or a region: https://de.sentry.io

# Bugsnag — a personal auth token (My account → Personal auth tokens)
LARAPILOT_BUGSNAG_AUTH_TOKEN=
LARAPILOT_BUGSNAG_PROJECT_ID=            # Project settings → General

# Flare — a personal access token with the read scope (write to resolve an error)
LARAPILOT_FLARE_TOKEN=
LARAPILOT_FLARE_PROJECT_ID=

# Datadog — an API key and an application key
LARAPILOT_DATADOG_API_KEY=               # or DD_API_KEY
LARAPILOT_DATADOG_APP_KEY=               # or DD_APP_KEY
LARAPILOT_DATADOG_SITE=datadoghq.com     # or DD_SITE: datadoghq.eu, us3.datadoghq.com, …
LARAPILOT_DATADOG_SERVICE=               # the service tag; empty = APP_NAME
LARAPILOT_DATADOG_SOURCE=error_tracking  # or logs: the error logs of two weeks
LARAPILOT_DATADOG_TRACK=trace            # in Error Tracking: trace (APM), logs, rum

# Rollbar — a project access token with the read scope (write to resolve an item)
LARAPILOT_ROLLBAR_ACCESS_TOKEN=

# Honeybadger — the personal authentication token of a user (profile → Authentication)
LARAPILOT_HONEYBADGER_AUTH_TOKEN=
LARAPILOT_HONEYBADGER_PROJECT_ID=        # as in the address of the project page

# AWS CloudWatch Logs — read with the AWS CLI signed in on the machine: no key is kept
LARAPILOT_CLOUDWATCH_LOG_GROUP=          # the log group the application writes to
LARAPILOT_CLOUDWATCH_REGION=eu-west-1    # or AWS_DEFAULT_REGION
LARAPILOT_CLOUDWATCH_PROFILE=            # optional: a profile of the AWS CLI
LARAPILOT_CLOUDWATCH_FILTER="?ERROR ?Exception ?CRITICAL"

# Every tracker
LARAPILOT_ERRORS_CACHE=300               # seconds the dashboard keeps what it read
LARAPILOT_ERRORS_TIMEOUT=15              # seconds a call to the tracker may take
Terminal
php artisan larapilot:settings-set --errors=YES --errors-provider=sentry
php artisan larapilot:errors-status

*_PROJECT_NAME (Bugsnag, Flare, Rollbar, Honeybadger) is optional, and gives the report its title. A provider Larapilot does not know, written by hand in config.yaml, is named by errors-status with the ones it does.

One entry for each bug

Boogle keeps one row for each time an exception is thrown, and gives each a code (#BUG12, #OUT3). Fourteen rows of one exception at one line are one bug thrown fourteen times, and that is how Larapilot shows them. A tracker that groups by itself — Sentry, Bugsnag, Flare, Rollbar, Honeybadger, Datadog Error Tracking — answers with the bug and its count: both are kept as they are, under the code the tracker gives (SHOP-1A, #RB57, #FL9001).

What How it is read
One bug The rows that share the exception, the file, and the line. OPEN and READ in Boogle are both open: seen is not fixed
Where A file of the server (/home/forge/…/releases/20260920/app/Services/CustomerImporter.php) is read as the file of the repository it is (app/Services/CustomerImporter.php:88): the longest end of the path that exists in the checkout. A line in vendor/ is said to be in a package
Request The method and the path the bug was thrown on the most, and on how many other routes. An id, a token, or an address in the path becomes {id}, {token}, {address}
Thrown How many times, the first, the last, and — with Boogle — how many nobody opened there yet
Outage What the uptime monitor of Boogle recorded (#OUT…). Listed apart: an outage is not a bug of the code by itself

The skill

in the editor
/larapilot-error
  1. Tracker — with the errors off, one question: which tracker records them — Boogle, Sentry, Bugsnag, Flare, Datadog, Rollbar, Honeybadger, CloudWatch — or none. The skill never chooses for you; your answer is saved with settings-set --errors=YES --errors-provider=…. Ask for another tracker and it asks again.
  2. Status — one line: tracker, project, address, whether the uptime is watched. With a credential missing, the skill says which variable and where its value is created; with anything else missing, the hints are printed and the skill stops.
  3. Download — errors-list --new --kind=error --report. The bugs nobody decided about are shown in a table, the ones thrown the most first, 15 rows at most; the detail of all of them goes to .larapilot/docs/support/errors.md (dashboard route /larapilot/errors/errors.md; boogle.md is kept as an alias). A bug that came back after its fix is said first.
  4. Scope — one question: the three thrown the most and the ones that came back, every one, the ones you name, or none.
  5. Confirm — bug by bug (or in batches when there are many): resolve, ignore with a reason, or skip for now. Ignores are recorded immediately with errors-link.
  6. Plan — errors-plan --codes=… groups the confirmed codes by domain (application, package, outage) and by place in the code: the same exception thrown in the same folder is one group, so one spec fixes it. You approve the groups. Outages and errors in a package never merge with application code, and a bug the tracker gives no file for stays alone.
  7. Read the code first — every where in the group is opened in the repository before the handoff. When it is in a package, the call of the application that leads there is found.
  8. Hand off to triage — each group goes to /larapilot-triage in the same turn, with the block below. /larapilot-bug then writes the fix spec, with a test that throws the same exception before the fix.
  9. Record — errors-link BUG12,BUG15 --spec=US-012, with every code of the group. A decision is about the bug, so one code for each bug is enough, however many times it was thrown.
  10. Close in the tracker — only when the fix is released and you say so: errors-resolve BUG12, where the tracker can be closed from Larapilot.
what triage receives
Production error
codes: #BUG12, #BUG15
class: Illuminate\Database\QueryException
message: SQLSTATE[23000]: Integrity constraint violation
where: app/Services/CustomerImporter.php:88, app/Services/CustomerImporter.php:102
request: POST /admin/customers/import
thrown: 14 times
returned: false

What a bug can be

State Meaning
new Open in the tracker, and nobody decided about it
in_backlog A spec of the backlog fixes it. Not fixed yet: it stays in the list until the tracker holds it as fixed
ignored Left as it is, with a reason of at least a sentence, given by the user
back after the fix Closed in the tracker with errors-resolve, then thrown again. The fix did not hold: it is shown first, and --new lists it again
no longer open Decided about here, and the tracker holds it no more as open

A decision is kept under what the occurrences of a bug share — the exception, the file, the line — and not under a code. The next time the bug is thrown, under a code nobody has seen, it is known and not handed over again.

Commands

Command What it does
larapilot:errors-status Setting, tracker, credentials, project, whether it can be closed from here, hints. It asks the tracker only while the errors are on, and writes nothing
larapilot:errors-list The open errors, one entry for each bug, with their state. --new only the ones with no decision and the ones that came back · --kind=error or outage · --limit= · --report writes {paths.support}/errors.md
larapilot:errors-plan --codes=BUG12,BUG21 Groups the confirmed codes by domain and place in the code, one triage handoff for each group. It only reads
larapilot:errors-link BUG12 --spec=US-012 The spec that fixes the bug that code belongs to. A code the tracker does not hold is refused
larapilot:errors-link BUG21 --ignore --reason="…" Leaves a bug as it is, and keeps why
larapilot:errors-link BUG21 --forget Drops what was decided: the bug is new again. Takes the key of the bug too, which is all that is left of one the tracker closed
larapilot:errors-resolve BUG12 Writes to the tracker. Closes the bug there: resolved in Sentry, Flare, Rollbar, Honeybadger, and Datadog Error Tracking, fixed in Bugsnag. In Boogle every open occurrence becomes FIXED (--status=DONE for the other word Boogle has), with Fixed by US-012. in its history, or the line given with --comment=. Refused for CloudWatch and the logs of Datadog, before anything is touched

errors-status, errors-list, and errors-plan are allowed through the MCP RunArtisanTool; the two that write — errors-link to the ledger, errors-resolve to the tracker — are not. The names the commands had when Boogle was the only tracker still answer: boogle-status, boogle-errors, boogle-plan, boogle-link, boogle-resolve.

On the dashboard

/larapilot/errors is always under Insights. With the setting off it says what Boogle is and how to connect it, and lists under it the other trackers that can be read instead. With the setting on it names the tracker it read and opens on what is open, in a sentence — 5 open errors, thrown 33 times: 2 with no decision yet. 1 came back after its fix — then four figures. For a tracker that records every throw — Boogle, CloudWatch, the logs of Datadog — the errors are then drawn day by day over the last two weeks, with the same numbers as a table. Every bug is a row: how many times it was thrown, the exception and what it said, its codes in the tracker, the file and the line, how long ago it last happened, and the decision. A row opens on the whole message, the route, the first and the last time, and what to do next. Bugs filter by decision and by text. What was read is kept for five minutes (LARAPILOT_ERRORS_CACHE); Read again asks the tracker anew, and Download report (.md) saves the same document the skill writes, named after the tracker and the day. When the tracker cannot be read the page says what to check, and the rest of the dashboard is untouched.

What is kept, and where

What Where Committed
What reads the tracker: address, token, keys .env Never
Decisions: exception, file and line, codes, spec, reason .larapilot/boogle.yaml Yes — the team shares one set of decisions
The report: every open bug with its decision .larapilot/docs/support/errors.md Your call: it quotes what the application said when it failed
The user, the query string, the payload of a request The tracker, and nowhere else —
The key and the token of the project, which Boogle sends with the list of projects Compared with BOOGLE_PROJECT_KEY in memory, and dropped —
Personal data stays in the tracker. An exception is recorded with the user who met it and the request that caused it. None of it is needed to fix a bug, so none of it is read: not into a file, not into the report, not into the cache, not into the chat. An address or a long secret quoted in a message is masked, whichever tracker it comes from. The token of Boogle reads every project of that Boogle, because Boogle gives tokens to users and not to projects: like every credential here, it lives in .env and in the secrets of the CI.

SBOM, CVE & Checkpoint

Two local views of security next to Aikido: the SBOM of every package the project ships, checked against OSV.dev for known vulnerabilities, and the last scan of Checkpoint.

The SBOM

larapilot:sbom reads the lockfiles — composer.lock, the JavaScript lockfile of the repository (npm, pnpm, Yarn, Bun), and the frontend companion's, its own or the workspace's in a monorepo — and lists each package with its version, direct or transitive, production or development, license, and package URL. Nothing is installed or downloaded. --write=both saves sbom.md and sbom.cdx.json (CycloneDX 1.5) under paths.security.

The SBOM page: 148 Composer packages with licenses, checked against OSV.dev
The 148 Composer packages of Fernway's lock file with their licenses, checked against OSV.dev: nothing open.

Known vulnerabilities

check, decide, gate
php artisan larapilot:vendor-audit --report --gate
php artisan larapilot:vendor-link GHSA-xxxx-xxxx-xxxx --spec=US-012
php artisan larapilot:vendor-link GHSA-xxxx-xxxx-xxxx --waive --reason="Never fed user input."
  • Only the name and the version of each package leave the machine. No account, no key.
  • Severity is the advisory's word, else its CVSS 3 score. Each vulnerable package comes with the version that fixes all its advisories and the command that moves to it — an update when its constraint already allows the fix.
  • --gate exits 1 on an open advisory at --fail-on (default high) or above; /larapilot-ship runs it. A waived advisory does not count; one in the backlog does until the update lands. Decisions and the trend live in .larapilot/vendor-audit.yaml: commit it.
  • /larapilot-vendor-check runs it with you, package by package.

Checkpoint on the dashboard

Security has two tabs: Aikido and Checkpoint. The second shows the last scan — verdict, checks by area (dependencies, configuration, code), each finding with its suppression hash, the trend — with Run the scan and a Markdown report. php artisan larapilot:checkpoint-scan runs checkpoint:scan --json (--only=, --skip=, --report, --gate) and keeps the result in .larapilot/cache/checkpoint/, out of git, because the details can quote code. With security_scan=YES, review and ship run it and stop on a failed check.

External frontend repo

Laravel is the only Larapilot cockpit. When topology is API + external frontend, the UI lives in another repository — one app, or a monorepo shared with other products — in Angular, React / Next.js, Vue / Nuxt, or Svelte / SvelteKit. Larapilot finds the projects that are this product's, reads the rules the frontend team wrote for agents, measures how its code is written, and implements UI there via repo: frontend tasks — or hands the work to that team. The FE repo holds application code only — no mirrored PRD.

Topology choices

Topology UI location Split repo?
Laravel-coupled Blade / Livewire / Inertia in Laravel No
SPA-in-Laravel Vite SPA inside Laravel No
API + external frontend Separate repository; Laravel is API (+ optional admin) Yes

PRD fields

Technical Architecture
**Frontend Topology:** API + external frontend
**Frontend stack (in-repo):** N/A
**External frontend repo:** acme/frontend (path in LARAPILOT_FRONTEND_REPO_PATH — never here)
**External frontend stack:** Angular
**Frontend projects:** portal, portal-admin
**Frontend delivery:** driven

Where the settings are stored

The absolute FE directory is stored in LARAPILOT_FRONTEND_REPO_PATH inside .env — never in config.yaml, whatever else frontend-set changes (a path an older version left in the YAML moves to .env). The stack, the target projects, and the mode (driven or handoff) live in config.yaml → frontend. The context envelope carries them as data.frontend.

What the scan reads

Part What it gives the agent
Workspace Nx (the graph of the nx the workspace installed, cached until a manifest or the commit moves; read from the files otherwise, with the targets Nx plugins infer), Angular CLI, pnpm / yarn / npm / bun workspaces, Turborepo, Lerna, Rush, or one app; the package manager of the lockfile; the Node version. Nothing is downloaded or installed.
Target projects The ones this product owns, named by the user once. Each with its stack and installed version, targets, tags, selector prefix, and the projects it depends on.
Write scope Libraries only the targets use are owned; one another application also uses is shared — it changes only when a task names it, and affected runs after.
Agent rules AGENTS.md at any depth, CLAUDE.md with its @imports, GEMINI.md, Cursor (.cursorrules, .cursor/rules/*.mdc with globs / alwaysApply / description), GitHub Copilot (copilot-instructions.md, *.instructions.md with applyTo), Windsurf, Cline, Junie, Kiro, Amazon Q, Roo, JetBrains AI — each with the folder it governs and how it applies; a symlinked CLAUDE.md is read once.
Observed conventions Counted on the code: standalone or NgModule components, @if or *ngIf, signal inputs or decorators, inject(), OnPush, zoneless; <script setup> and Pinia store style; Svelte runes; App Router files; test naming, styles, file names; generator defaults. Recent files of each kind become the models for new ones.
Commands nx run portal:test, nx affected -t lint test build, ng test --watch=false, turbo run --filter, pnpm --filter, … — non-interactive, from the frontend root. Generators: the team's own first.
API client orval, openapi-generator, ng-openapi-gen, hey-api, openapi-typescript, kubb, RTK Query codegen — with their input and output and the command that regenerates them — or the hand-written calls.

An app in its own repository, built inside a monorepo

Some teams keep each app in a repository of its own and build it inside the monorepo they share: the app holds a project.json and no nx.json, and its tsconfig extends the monorepo's. Cloned at its place inside the monorepo, the scan finds the monorepo among its parent folders; checked out somewhere else, larapilot:frontend-set --workspace=/absolute/path links it (LARAPILOT_FRONTEND_WORKSPACE_PATH in .env). Every path of the scan is then relative to root, the monorepo; commands run in run_in; commits go to the app's own repository (git_root), where the monorepo's affected cannot see them, so the scan leaves it out. Out of reach, run_in is null and the scan says where the app expects to sit. An Angular CLI app cloned inside a monorepo is a workspace of its own, but the agent rules of the monorepo around it still apply: they come under rules.inherited.

Checks that run, and commits that look like the team's

Karma runs headless — the launcher the karma config defines, or ChromeHeadless — with --watch=false. A target the installed CLI can no longer run is left out of the commands and named in unavailable: the TSLint builder left the Angular CLI in v13, Protractor in v19, and once the packages are installed their own builder catalog decides. A project with no spec file, or whose generators skip them, is a question for the user, recorded in the decision journal. The scan reads the commit history (git.commits): Conventional Commits or not, types, scopes, the language of the subjects, commitlint and hooks; a task's commit follows it with US-012 TASK-03 in the subject. Vendored packages — built third-party code copied into the repository — are listed under write_scope.vendored and never edited.

The frontend team's rules win on code

The editor loads the agent rules of the workspace it opened — the Laravel one — never those of the frontend repository. So implement reads every rule that governs the target projects before its first frontend edit, and asks frontend-rules --file=… before each task which rules govern the files it writes: a Cursor rule on *.component.ts, a Copilot rule on **/*.spec.ts, an AGENTS.md deep in a feature folder. Inside the frontend repository those rules decide structure, naming, state, tests, commits; Larapilot decides only the workflow. A deeper folder wins; two rules that disagree go to the user. Review checks the diff against the same rules.

Playbooks

For what the rules and the code leave open, one playbook per stack — Angular, React (Next.js, React Router, React Native), Vue (Nuxt), Svelte (SvelteKit) — says what each major version adds, so an Angular 15 workspace never receives Angular 20 code.

CLI commands (all from Laravel)

Command When
larapilot:frontend-set --path=… [--stack=Angular] Once — writes LARAPILOT_FRONTEND_REPO_PATH to .env
larapilot:frontend-set --project=portal [--project=…] A monorepo: the projects of this product (checked against the workspace; --clear-projects forgets them)
larapilot:frontend-set --workspace=… An app in its own repository whose monorepo is not among its parent folders — LARAPILOT_FRONTEND_WORKSPACE_PATH in .env (--clear-workspace)
larapilot:frontend-set --mode=driven|handoff Who writes the frontend: Larapilot, or the frontend team from a brief
larapilot:frontend-scan Once per spec, before plan and implement — --project=, --full, --no-cli, --fresh
larapilot:frontend-rules --file=… Before writing — the rules that govern those files
larapilot:frontend-brief US-012 Handoff — the story, the frontend tasks, the API operations they call, and the mockups, in .larapilot/docs/frontend-briefs/ (--stdout returns it instead)

Delivery

  • Laravel — PRD, backlog, plans, mockups, workflow. All /larapilot-* skills.
  • Frontend repository — application code from larapilot-implement tasks with repo: frontend (and project: in a monorepo), committed with its hooks on and US-012 TASK-03 in the subject. task-done finds that commit in the frontend repository. No Larapilot workflow there.
  • Handoff — in handoff mode implement runs the backend tasks and writes the brief; the frontend team builds in its own session, where its rules load by themselves, and names its commits.
  • Mockups — always in Laravel .larapilot/mockups/; Joe implements them in the FE repository.

End-to-end

Walkthrough: External frontend repo example.

  1. Inception → API + external frontend + absolute path → frontend-set
  2. frontend-scan → in a monorepo, name the projects → frontend-set --project=…
  3. /larapilot-spec → plan → implement (BE + FE tasks from Laravel, or the brief in handoff)
PRD edits happen on Laravel only. The FE repo never holds a mirrored PRD.

Design systems

Five packaged visual references ship in .larapilot/design-systems/ on larapilot:install and refresh on larapilot:update. /larapilot-design picks the folder that matches the PRD stack choice — Elise and Joe enforce token and component consistency through implement and review.

System Path When to use Contents
Filament design-systems/filament/ Admin/control panel mockups when the PRD records Filament tokens.css, components.md, figma-sources.md, 17 HTML screens — v3 light sidebar, slate primary
Laravel Starter Kits design-systems/starter-kit/ Authenticated app UI when the PRD records a Starter Kit variant (livewire, react, vue, svelte) tokens.css (shadcn oklch), components.md, 7 HTML screens
Bootstrap 5 design-systems/bootstrap-5/ Marketing or app UI when the PRD records Bootstrap 5 (without Filament or a Starter Kit) tokens.css, components.md, 6 HTML screens (landing, dashboard, login, settings, components)
Tailwind CSS design-systems/tailwind/ Marketing or custom app UI when the PRD records Tailwind CSS without Filament, Starter Kit, or Bootstrap tokens.css, components.md, 6 utility-first HTML screens
AdminLTE design-systems/adminlte/ Admin/control panel mockups when the PRD records AdminLTE 4 tokens.css, components.md, 6 HTML screens (dashboard, resource list, login, settings, components)

Open each folder's html/index.html locally as a visual catalog — or browse every project mockup in the dashboard Design gallery (/larapilot/design). Skills resolve paths via php artisan larapilot:config-show → paths.design_systems.

Sign-in screens: the fields are drawn, never built

A mockup is a picture of the screen, not a working form. Password managers (1Password, Bitwarden, iCloud Keychain, the browser's own) scan every <input> they find, and the Design gallery renders the same screen several times at once — the card, the viewer, the style comparison. One real sign-in field makes them pop up, fill in, and offer to save on every preview. So /larapilot-design follows one hard rule, and the five packaged systems ship a login.html that shows how:

  • No <input>, <textarea>, or <form> for credentials — username, login email, password, confirm password, PIN, one-time code — on login, register, reset, two-factor, change-password, and create-user screens.
  • No workaround counts. A masked type="text", autocomplete="off", data-1p-ignore, or a renamed label are all ignored by password managers, which guess from the layout.
  • The field is drawn: a <div> styled like the input of the design system, holding sample text (jane@example.com, ••••••••••), with role="img" and an aria-label. Its label is a <span>.
  • The button is a link to the next screen (<a role="button" href="dashboard.html">), so the flow stays clickable in the viewer. Error, empty, and focus states are separate screens.
  • Every other control stays real: search, name, amount, select, the Remember me checkbox.

Filament and Starter Kit carry the classes in tokens.css — .mock-label, .mock-field, .mock-field--secret, .mock-field--empty. The real Fortify or Filament fields are built by Alex at implementation time.

Custom design systems & templates

You can add your own design system or UI template under .larapilot/design-systems/{your-name}/ and use it for mockups (/larapilot-design) and implementation — not only the five packaged references.

  • Drop tokens, component notes, HTML starters, and brand assets in that folder (same shape as the packaged ones helps: README.md, tokens.css, components.md, optional html/).
  • Record the choice in the PRD (or tell the agent during inception/design) so Elise, Joe, and Alex treat it as the source of truth for mockups and shipped UI.
  • larapilot:update refreshes only the packaged systems — your custom folder is never overwritten.

Code quality

Every Larapilot project stays compatible with Larastan (PHPStan level 5+) and Laravel Pint. The gate is installed with the package and is not optional.

What install scaffolds

Artifact Purpose
phpstan.neon.dist Larastan extension, level: 5, paths app/ · bootstrap/ · config/ · database/ · routes/ · tests/
pint.json Laravel preset — code style linter/formatter
composer.json require-dev: larastan/larastan, laravel/pint; scripts lint, lint:check, analyse

Run the gate

Check-only (CI / before merge)
php artisan larapilot:quality
Apply Pint formatting, then Larastan
php artisan larapilot:quality --fix

Equivalent Composer shortcuts after composer install:

composer.json scripts
composer lint:check   # pint --test
composer analyse      # phpstan analyse --memory-limit=1G

When to run

  • Implement — /larapilot-implement runs larapilot:quality on Laravel tasks before each task-done
  • CI — add php artisan larapilot:quality (or composer lint:check && composer analyse) on every PR; failing Pint or Larastan blocks merge
  • Health check — larapilot:doctor reports quality_pint, quality_larastan, and quality_packages; install is not healthy when the gate is missing

Rules

  • Minimum level 5 — never lower level in phpstan.neon(.dist) without an explicit human waiver
  • Legacy codebases — generate a baseline with vendor/bin/phpstan analyse --generate-baseline (see Larastan docs); do not drop below level 5 for new code
  • larapilot:install --skip-composer — write config files only; run composer require --dev larastan/larastan laravel/pint yourself
Andrew + Jack own the gate — Pint for style, Larastan for static analysis. Robert flags regressions at review when quality was skipped.

Artisan CLI

Skills save everything through php artisan larapilot:* commands. You do not need to learn them: they are how the agent writes files, checks them, and moves a story from one status to the next. Run one by hand for debugging, scripting, or CI — for example php artisan larapilot:doctor --human to check the install.

JSON envelope

stdout — every command
{ "schema": "larapilot/v1", "kind": "plan_result", "data": { "code": "US-001", "task_count": 4 } }

{ "schema": "larapilot/v1", "kind": "error",
  "error": { "code": "E_PRECONDITION", "message": "…", "hint": "…" } }

Skills never invent persistence — they always go through these commands and parse the envelope. Errors carry a stable code and a matching exit status:

Error code Exit Typical cause
E_INVALID_INPUT 2 Bad flag value, invalid payload, a custom skill name that is not kebab-case
E_CONNECTOR 3 The persistence connector failed
E_PRECONDITION · E_NOT_FOUND 4 A guarded transition (implement before plan, release command with release_mode=NO), an unknown spec or file
anything else (e.g. E_QUALITY) 1 Pint or Larastan findings — carried on error.details

Ask for the slice you need

Envelopes stay small on purpose, so an agent's context does not fill up with data it will not use:

Terminal
php artisan larapilot:context implement                              # settings, paths, project facts, files to read
php artisan larapilot:context review --session=k3f9x2                 # same conversation: only what is new
php artisan larapilot:context plan --fresh                            # after a compaction: everything again
php artisan larapilot:spec-list                                       # the backlog without the bodies (--full for all)
php artisan larapilot:prd-show                                        # outline of the PRD: sections, ids, MoSCoW
php artisan larapilot:prd-show --ids=FR-004,J-001 --section="Technical Architecture"
php artisan larapilot:config-show --only=tracker                      # a slice context does not carry: tracker, backstage, workflow, personas
php artisan larapilot:spec-show US-004 --fields=id,title,status,dependencies
php artisan larapilot:spec-show US-004 --task=TASK-02                 # one task only
php artisan larapilot:spec-next --status=PLANNED --fields=id,title
php artisan larapilot:quality -v                                      # raw Pint/Larastan output inside the JSON
php artisan larapilot:doctor --human                                  # a table instead of JSON (also metrics --human)

Command reference

Category Commands Used by
Setup larapilot:install (--force, --skip-composer) · larapilot:update (--skip-boost, --preserve-design-systems) · larapilot:doctor · larapilot:context (--session=, --fresh, --with=) · larapilot:config-show (--only=) · larapilot:settings-set · larapilot:quality (--fix) Install, upgrades, health check, code quality, project settings
Access larapilot:dashboard-user (list · add <user> · remove <user>, --password=) Dashboard auth
Forges & notify larapilot:github-status · larapilot:gitlab-status · larapilot:bitbucket-status · larapilot:azure-status · larapilot:notify (--event=, --title=, --body=, --url=) Forges & notifications
Runtime larapilot:logs (--group, --level=, --search=, --since=, --limit=, --files, --file=) · larapilot:diagnostics (--lines=, --no-logs) /larapilot-bug and /larapilot-error, local triage — see Diagnostics and logs
PRD & discovery larapilot:prd-write · larapilot:validate-prd · larapilot:prd-show (--ids=, --section=; no option for the outline) · larapilot:prd-impact (--ids=FR-004,J-001; omit for the whole PRD) · larapilot:choices-set (--from-prd, --business-model=, --server-management=, …) · larapilot:frontend-set (--project=, --mode=) · larapilot:frontend-scan · larapilot:frontend-rules (--file=) · larapilot:frontend-brief /larapilot-inception, /larapilot-adopt, /larapilot-feature, /larapilot-bug, /larapilot-prd, /larapilot-spec, /larapilot-frontend-companion
Backlog larapilot:spec-list (--status=, --full for the bodies) · larapilot:spec-add · larapilot:spec-show · larapilot:spec-next · larapilot:spec-delete · larapilot:validate-spec · larapilot:spec-comment (--blocks-merge) /larapilot-spec, shortcuts, dashboard comments
Design larapilot:mockup-choose-style US-XXX --style= /larapilot-design style variants — same as Use this style on the Design page
Planning larapilot:spec-plan · larapilot:validate-plan /larapilot-plan
Execution larapilot:spec-start · larapilot:quality (--fix) · larapilot:task-done (--commit=) · larapilot:spec-review /larapilot-implement — see Code quality
Review larapilot:spec-approve (--commit=, --force) · larapilot:spec-request-changes (--include-feedback) /larapilot-review
Traceability larapilot:decision-log · larapilot:decision-check · larapilot:code-log · larapilot:code-history (--file=, --spec=) Every skill — see Decision journal and Code history
Metrics larapilot:metrics Dashboard, progress reporting; same data as GET /larapilot/api/metrics
Usage (Lucille) larapilot:usage-log · larapilot:usage-report (--insights, --format=json|md|human) · larapilot:schedule-set · larapilot:schedule-show · larapilot:schedule-apply (--dry-run) /larapilot-usage, /larapilot-schedule, inception deadlines — see Usage
Economics (Aurora) larapilot:economics-set · larapilot:economics-show (--format=json|md|quote) · larapilot:economics-market-write · larapilot:economics-quote-write (--lang=) /larapilot-economics — see Economics
Release ledger larapilot:release-list · larapilot:release-add (--semver=, --title=, --status=, --specs=) · larapilot:release-set (--semver=, --status=, --add-spec=, …) · larapilot:release-cut · larapilot:release-feature (--spec=, --slug=) · larapilot:release-sync · larapilot:release-ship (--push) · larapilot:release-import (--dry-run) /larapilot-release, inception/adopt/feature/ship when release_mode=YES — see /larapilot-release
Custom skills larapilot:custom-skill-list · larapilot:custom-skill-add (--name=, --file= / --content= / stdin, --force) /larapilot-custom-skill — see Custom skills
Workflow hooks larapilot:hook-list (--event=) · larapilot:hook-run {event} (--phase=, --spec=, --task=, --release=, --dry-run) · --skill-hooks-done= on every command that fires an event The skills that move a spec, write the PRD, or ship, when hooks=YES — see Workflow hooks
Developer portal larapilot:backstage-export (--write, --force, --no-techdocs, --file=, --api-base=) /larapilot-backstage, CI catalog refresh — see Backstage portal
Project tracker larapilot:tracker-status (--ping) · larapilot:tracker-push (--dry-run, --spec=, --force) · larapilot:tracker-pull (--apply) /larapilot-tracker, CI backlog sync — see Project trackers
Aikido larapilot:aikido-status · larapilot:aikido-issues (--new, --severity=, --type=, --limit=, --report, --gate) · larapilot:aikido-plan (--ids=) · larapilot:aikido-link (--spec=, --waive --reason=, --forget, --local) · larapilot:aikido-push · larapilot:aikido-repos (--search=, --use=, --forget) · larapilot:aikido-register · larapilot:aikido-scan /larapilot-aikido, /larapilot-ship, CI gate — see Aikido
Production errors larapilot:errors-status · larapilot:errors-list (--new, --kind=, --limit=, --report) · larapilot:errors-plan (--codes=) · larapilot:errors-link (--spec=, --ignore --reason=, --forget) · larapilot:errors-resolve (--status=, --comment=) /larapilot-error, /larapilot-ship — see Production errors
Stack & upgrades larapilot:stack (--only=, --no-db) · larapilot:upgrade-check (--laravel=, --php=, --php-from=, --db=, --db-from=, --offline, --report, --gate) The upgrade skills, the About page — see Laravel, PHP & DB upgrades
Dependencies larapilot:sbom (--full, --write=) · larapilot:vendor-audit (--cached, --new, --report, --fail-on=, --gate) · larapilot:vendor-link (--spec=, --waive --reason=, --clear) · larapilot:checkpoint-scan (--only=, --skip=, --cached, --report, --gate) /larapilot-vendor-check, /larapilot-review, /larapilot-ship — see SBOM, CVE & Checkpoint

MCP server

The larapilot MCP server runs beside laravel-boost. Skills write through the CLI; MCP is for reading during the conversation.

Tool Does
BacklogListTool Specs with code, title, priority, status — optional status filter
SpecShowTool One spec with its plan tasks and working directory
DiagnosticsTool App status, health checks, redacted log tail — see Diagnostics
RunArtisanTool Read and validate commands only: config-show, spec-list, spec-show, spec-next, metrics, usage-report, decision-check, code-history, the four forge *-status probes, validate-prd/-spec/-plan, prd-impact, prd-show, doctor, diagnostics, logs, quality, frontend-scan, frontend-rules, backstage-export, tracker-status, hook-list, context, schedule-show, stack, upgrade-check, sbom, vendor-audit, aikido-status / aikido-issues / aikido-plan / aikido-repos, errors-status / errors-list / errors-plan (and their old names boogle-status / boogle-errors / boogle-plan). Anything else is refused

RunArtisanTool checks the parameters too. Each command takes through MCP only the parameters that read, and an option that writes a file is refused before the command runs: quality --fix, backstage-export --write / --force / --catalog= / --mkdocs= / --file=, usage-report --output=, aikido-issues --report, aikido-repos --use= / --forget, errors-list --report, upgrade-check --report, sbom --write=, vendor-audit --report. Run directly with Artisan, the commands take every option as before. All four tools are annotated as read-only (readOnlyHint), and the schema of command lists the commands that are allowed.

Dashboard

The workspace in .larapilot/, as pages your own Laravel app serves at /larapilot. It is where you read what the skills write: review backlog progress, read the PRD, browse the Design gallery, inspect settings/choices, Lucille usage, and Economics quotes — then drill into spec detail between skill sessions. With comments=YES you can also post internal feedback from the spec page (dev/staging only), the File manager uploads and organizes the material the skills read, Database shows the tables of the application and what is in them, and Logs reads what it logged — workflow state still changes only via skills/CLI.

Served by your Laravel app

  • Nothing else to run. The pages are routes of your application, in its web middleware group. Herd, Valet, Sail, php artisan serve: whatever already serves the app serves the dashboard.
  • Nothing to build. Blade views that carry their own styles: no Node, no npm run build, nothing added to the asset pipeline of your app.
  • No account. It reads .larapilot/, the Git history of the repository, and the database in .env: nothing to sign up for, nothing hosted somewhere else.
  • Current on every reload. Each page reads the files when it loads, so what a skill wrote a minute ago is there. No sync, no export.
  • Off where it should be. It answers only in the environments you allow, never in production, and dashboard_auth puts a password in front of it on a shared staging server.

Activation

The dashboard is on by default after php artisan larapilot:install. Routes register automatically when the package boots. All of the following must be true:

  • LARAPILOT_ENABLED=true (default) — master switch in config/larapilot.php
  • LARAPILOT_DASHBOARD_ROUTE=true (default) — dashboard route toggle
  • App environment is not production
  • Environment is in the allowlist: local, development, testing, or staging

Start your Laravel app the way you already do and open /larapilot on it:

open the dashboard
php artisan serve    # → http://127.0.0.1:8000/larapilot
# Herd or Valet      # → http://your-app.test/larapilot
# Sail               # → http://localhost/larapilot

If routes 404, check APP_ENV and the flags above. The prefix below moves it elsewhere.

config/larapilot.php — dashboard_route
'dashboard_route' => [
    'enabled' => env('LARAPILOT_DASHBOARD_ROUTE', true),
    'prefix' => 'larapilot',           // change to customize URL prefix
    'middleware' => ['web'],
    'environments' => ['local', 'development', 'testing', 'staging'],

    // Optional HTTP Basic Auth on the UI — enforced only when the
    // `dashboard_auth` project setting is ON. Never gates the JSON API or MCP.
    'auth' => [
        'file' => base_path('.larapilot/auth.yaml'),   // hashed credentials, git-ignored
        'realm' => env('LARAPILOT_DASHBOARD_AUTH_REALM', 'Larapilot'),
        'max_attempts' => (int) env('LARAPILOT_DASHBOARD_AUTH_MAX_ATTEMPTS', 30),
    ],
],

Authentication opt-in

The dashboard UI is open by default in the allowed environments. Turn on the dashboard_auth project setting to require HTTP Basic Auth:

enable the dashboard gate
php artisan larapilot:dashboard-user add andrea      # prompts for a password, stores only the hash
php artisan larapilot:settings-set --dashboard-auth=YES

Credentials are argon2id/bcrypt hashes in .larapilot/auth.yaml (no database, no User model; added to .gitignore automatically). Failed sign-ins are rate-limited per IP. This gate is UI-only — it never applies to /larapilot/api/* (use LARAPILOT_API_TOKEN) or the MCP server. Full reference: Project settings → Dashboard auth.

Pages

Tab URL What you see
Board /larapilot Kanban backlog — specs grouped by workflow status (TODO → DONE), the status as it stands in one Markdown file (/larapilot/board.md), and the epics as an outline of stories and tasks (/larapilot/epics.md)
PRD /larapilot/prd Rendered PRD.md with a section table of contents, a search that looks in the PRD and nowhere else, the decision journal, and two downloads: the whole PRD (/larapilot/prd/prd.md) and the functional analysis summary (/larapilot/prd/functional-summary.md)
Inception /larapilot/inception Discovery choices snapshot from .larapilot/choices.yaml
Plan /larapilot/plan The delivery forecast: a dependency-aware Gantt with open work queued from today, by epic or in delivery order (time axis, today marker, task filters), milestones, and schedule criticality. Download /larapilot/plan/plan.md: epics, every story with status, priority, points, release, blockers, and forecast window, the tasks of each planned story, milestones, and the delivery order
Design /larapilot/design Every screen first, as a card; a click opens that mockup as a site you browse, with All screens to come back. Prev/next through every flow, style variants under mockups/{spec}/styles/{slug}/, each listed on its own, and Use this style on the open screen, download /larapilot/design/package.zip (HTML + assets)
Settings /larapilot/settings Current settings.* with allowed options and per-option explanations
Skills /larapilot/skills Every skill the agents of the project can run, whoever brought it: the project, Larapilot, another package, Laravel Boost, the folder of an agent. Click one to read it. Under them, the files the agents are told from, one part for each author
File manager /larapilot/files The five material folders, each with what it is for: browse the folder tree, preview and download files, read a PDF in the page, upload files or a whole folder with its structure, rename, delete. A sixth folder, Project, shows the application itself, read only
Database /larapilot/database The tables and views of the database in .env, whatever the driver — MySQL, MariaDB, PostgreSQL, SQLite, SQL Server. Open one for its rows, a page at a time, with sort, search, and a foreign key that leads to the row it points at; or for its structure: columns, indexes, foreign keys. Diagram draws the tables and the foreign keys between them; Migrations lists the ones that ran and the ones still to run. Download SQL dumps the whole database; Other formats gives its structure alone, Laravel migrations, and seeders with its rows. Read only; passwords and tokens are never shown
Logs /larapilot/logs The log files of the application read as entries, the newest first: by level, by period, searched, or with the repeats counted — one row for each thing logged. An exception shows where it was thrown, and the frames of your own code apart from the framework's. Download log for the file. Read only; passwords, tokens, and keys are shown as [REDACTED]
Laravel /larapilot/laravel How the framework is set up in the application and what it is doing, in five tabs. Overview: the drivers in use and what is cached. Schedule: the scheduled tasks, the next one due first. Queue: the jobs that wait, are delayed, or are held by a worker, and the ones that failed. Mail: the mail the application sent, as it left. Dumps: what dump() and dd() printed, with the line that dumped it. Nothing of the application is changed
Git /larapilot/git Last-12-month contribution heatmap, every branch measured against the branch it is heading for, and the history drawn as a graph — one lane per line of work, each commit on the branch it was made on. Filterable by developer
Usage /larapilot/usage Lucille token and hour ledger by category, plus download /larapilot/usage/report.md. Schedule and Gantt are on Plan
Security /larapilot/security Two tabs. Aikido: what Aikido found in the repository, with what was decided about each finding and the verdict of the ship gate; with aikido off, two lines on what Aikido is and how to connect it. Download /larapilot/security/aikido.md. Checkpoint (/larapilot/security/checkpoint): the last scan of Checkpoint — verdict, checks by area, findings with their suppression hash, trend — with Run the scan and /larapilot/security/checkpoint.md
SBOM /larapilot/sbom Every package of the lockfiles — Composer, the JavaScript of the repository, the frontend companion — with version, scope, license (copyleft flagged), abandoned packages, and the vulnerabilities of the last OSV.dev check grouped by package, with the fix and the decision. Check vulnerabilities runs it. Downloads: sbom.md, sbom.cdx.json (CycloneDX 1.5), vendor-audit.md
Errors /larapilot/errors What the running application threw, as the tracker of the project recorded it: one row for each bug, how many times it was thrown — day by day when the tracker records every throw — and what was decided. Always listed; with errors off, what Boogle is, how to connect it, and the other trackers that can be read instead. Download /larapilot/errors/errors.md (boogle.md still answers)
Economics /larapilot/economics What the project costs, what the client pays, and what is left (when account is FREELANCE or COMPANY): the short answer, every sum written as a receipt, where the money goes, and — for a subscription — when it comes back. Downloads: client quote /larapilot/economics/quote.md, internal report /larapilot/economics/report.md
API docs /larapilot/api/docs Interactive OpenAPI browser for the workflow JSON API
Docs /larapilot/docs Delivery loop, packaged Boost skills, and persona roster
About /larapilot/about What the project runs on: Laravel, PHP, the database server, and Node, each with its support window as a bar (bug fixes, security fixes, today) and an alert near or past its end; the project, extensions, connections, drivers, the packages that shape an upgrade, frontend, CI and deploy, and every file that pins a version

Pages are reached from a sidebar grouped as Workflow, Workspace, Insights, and Reference; on a phone the sidebar is a drawer behind the menu button. The dashboard follows the system theme, and the switch at the foot of the sidebar pins light or dark for that browser.

File manager

/larapilot/files manages what you hand the skills before they start. It opens on five folders you can change, and on the project itself, which you can only read:

The File manager: five workspace folders and the project, read-only
Brand, client materials, design systems, legacy snapshots, and custom skills — and the project itself, read-only.
Folder What it is for Read by
.larapilot/brand/ Logo, palette, typography, and the brand guide Design and mockups
.larapilot/client-materials/ Briefs, analyses, and documents supplied by the client Inception and specs
.larapilot/design-systems/ Visual references and tokens the mockups are built on Design and mockups
.larapilot/legacy/ Snapshots of the old system to port or migrate Adopt, inception, and parity checks
.larapilot/skills/ Your custom skills, one folder per slash command Boost slash commands
./ — Project read only The Laravel application itself: code, config, routes, and tests Plan, implement, and review
Action Brand · Client materials · Design systems · Legacy · Skills Project
Browse, preview, download Yes Yes
Upload files or a folder, create a folder Yes No
Rename a file or a folder Yes No
Delete a file or a folder Yes No

Inside a folder there is a folder tree on the left and the contents on the right. Markdown, text, and images are previewed in the page; any file can be downloaded. Upload folder, or a folder dropped on the page, keeps its structure: every file lands at the path it had inside the folder. A file that already exists is kept unless Replace existing files is on, and whatever was skipped is listed with the reason. The five folders themselves cannot be renamed or deleted, and the packaged design systems are marked, because larapilot:update rewrites them.

  • A PDF is read in the page. One page or two side by side (the first stands alone, like a cover), zoom with the buttons, + − 0 or Ctrl/Cmd + wheel, page jump, arrow keys, drag to move around an enlarged page, and full screen with F. The reader is PDF.js 3.11 from cdnjs, checked against its hash; where it cannot load — offline, or scripts off — the file opens in its own tab as before.
  • Project is read only. The routes that upload, create, rename, and delete do not know the folder, and the service refuses a write to it whoever asks. The code is changed in your editor and by the skills, never from a web page: an upload into public/ on a shared host would be code anyone signed in could run.
  • Folders that start with a dot are left out of Project — .git, .larapilot, .github, .idea, at any depth — and so are the caches of the framework, bootstrap/cache/ and storage/framework/: a cached configuration holds every value of .env. They are not listed, not counted, and answer 404 by address, a link that resolves into one included. Files that start with a dot are shown: .env.example, .gitignore, .editorconfig. vendor/ and node_modules/ are listed and can be opened; they are left out of the count and the tree unfolds only along the folder you are in.
  • Credentials show their keys, never their values. In every folder, a file that holds credentials is shown with each value replaced by ***************** — on screen and in the download, so the real bytes never leave the server. That covers .env and .env.* (a template such as .env.example is shown as it is), auth.json, .npmrc, .yarnrc, .netrc, .pgpass. Comments stay readable; a commented-out value and the later lines of a value written over several lines are masked too. A key or a certificate (.pem, .key, .p12, .pfx, id_rsa, …) is one line of asterisks. A database (.sqlite, .db) is listed, and neither shown nor downloaded. A .log file is shown and downloaded with the passwords, tokens, and keys it quotes replaced by [REDACTED], as on the Logs page, which reads it whole, by entries, where the file manager shows its first 256 KB.
  • Local by default. The file manager is open in local, development, and testing. On any other environment (staging) it is served only when dashboard_auth is YES, and answers 404 otherwise: client documents and legacy snapshots stay behind a sign-in.
  • Upload size. One file is limited by LARAPILOT_FILE_MANAGER_MAX_UPLOAD_KB (default 51200, 50 MB) and by PHP's upload_max_filesize and post_max_size, whichever is lower. The page shows the limit in force.
  • Nothing leaves the folder. A path outside the six folders returns 404, a symlink is listed and never followed, and an uploaded .html, .js, or .svg is never run in the dashboard.
  • Custom skills stay in step. A change under .larapilot/skills/{name}/ refreshes that skill's copies in .ai/skills/ and the agent folders; deleting the skill removes the copies that still match it.
  • LARAPILOT_FILE_MANAGER=false removes the page.

Database

/larapilot/database shows the application's own database: the connection named by DB_CONNECTION, with the DB_* values in .env. Everything goes through Laravel's schema and query builders, so the page is the same on MySQL, MariaDB, PostgreSQL, SQLite, and SQL Server, and on any Laravel version Larapilot supports. It reads, and never writes.

The rows of the bookings table, filtered and sorted
The rows of bookings, filtered and sorted on the server.
Migrations: two pending, eight ran over seven batches
Migrations: two still to run, eight ran over seven batches.
  • The list opens on every table and view, with its size where the driver reports one, and the driver, the database, and the host on top — never the password. On MySQL and MariaDB only the database in .env is listed, not every database the user can see; on PostgreSQL every schema is, and a table outside public is named schema.table. With a prefix on the connection, tables are named without it.
  • Rows: 50 to a page (LARAPILOT_DATABASE_VIEWER_PER_PAGE), ordered by the primary key. A click on a column sorts by it; the search looks in the text columns, without regard to case. A value in a foreign key column is a link to the row it points at. A click on a row opens it whole: long text in full, JSON indented, Copy as JSON, and Copy as SQL INSERT — the row as one INSERT statement of the driver, its columns named and its values written as the dump writes them, ready to run on a database with the same table; a hidden column goes out without its value, and the statement says which. A binary value is shown as hex with its size, NULL as NULL.
  • Structure: the columns with type, null, default, primary key, auto increment, and comment; the indexes; the foreign keys with what happens on update and on delete; and the create statement — the CREATE TABLE, or CREATE VIEW, that builds it, with its indexes and keys, in the SQL of the driver, with a button to copy it. It is the statement the dump writes for that table.
  • Diagram is the second view of the list (/larapilot/database?view=diagram): a box for each table with its columns — PK, FK, the type — and a line for each foreign key, from the column to the one it references, with an arrow on that end. A table stands to the right of the tables it points at, so the ones everything depends on are on the left; the tables and views no foreign key touches are in rows below. Keys only leaves the keys in each box and counts the other columns. A click on a table keeps it, its lines, and the tables at their other end lit, and dims the rest; its name opens it. −, +, and Fit zoom, as Ctrl or ⌘ with the wheel does; a drag moves around. The diagram is drawn on the server as plain SVG — no library, nothing fetched from outside — so it reads without scripts too, and it shows the structure alone, never a row. It is drawn for up to 300 tables and views. Download PDF saves the diagram as it is shown — all columns or keys only — as /larapilot/database-diagram.pdf: one page as large as the drawing, in vectors, so it stays sharp at any zoom and prints on whatever paper it is scaled to, with the database, the driver, the counts, and the day on top. The file is written by Larapilot itself, with the two Courier fonts every PDF reader has: no PDF library is installed, and a name with letters outside Western European shows a ? for each of them.
  • Migrations is the third view (/larapilot/database?view=migrations): which migrations ran and which are still to run, as php artisan migrate:status tells it — the files of the application and of its packages against the migrations table of the connection. The pending ones come first, then the ones that ran with their batch, the newest first, each with the day it was written; a migration that ran from a file that is no longer there is marked Ran · file gone. Before the first migrate the table is not there and every file is pending — the page says so — and it is offered on a database with no table yet. A field narrows the list by name, state, or package. Nothing is run from the dashboard: the page names the command, php artisan migrate, and the batch it would be.
  • Credentials are never shown. A column named like a password, a token, or a secret — password, remember_token, *_token, *secret*, two_factor_recovery_codes, *api_key*, *private_key* — is shown as *****************, and is never searched, sorted, or filtered on, so its value cannot be guessed one query at a time. database_viewer.masked_columns in config/larapilot.php adds patterns of your own.
  • Download SQL writes the whole database as one .sql file, in the dialect of the driver it came from, to restore with mysql, psql -f, sqlite3, or sqlcmd: the structure, every row, then what has to come after them. MySQL, MariaDB, and SQLite give their own CREATE statements — generated columns, AUTO_INCREMENT, SQLite triggers, and the next AUTOINCREMENT number come along, and a view loses only its DEFINER. PostgreSQL is rebuilt from its catalogs the way pg_dump does it, every name with its schema: schemas, enum types, serial and identity columns with their next value, generated columns; primary keys, unique and check constraints, indexes, foreign keys, and views follow the rows. SQL Server and other drivers are rebuilt from Laravel's schema builder. Each table and view is dropped first if it exists; triggers outside SQLite, functions, and grants are not part of the file.
  • The dump is written while it downloads, a thousand rows at a time, from one read-only snapshot: a large database neither fills the memory nor comes out half-updated. The file opens with the driver, the database, the date, and what was left out, and ends with -- Dump complete. — or with the error that stopped it.
  • Credentials stay out of the dump unless you ask: the hidden columns are written as NULL, or as an empty string where NULL is not allowed — as hidden-1, hidden-2, … where a unique index holds the column, so the file still restores — and the head of the file names them. Include passwords and tokens puts them in, and is offered only in local, development, and testing — on a shared host they never leave the server, sign-in or not. The seeders follow the same rule, and Copy as SQL INSERT never carries them.
  • Other formats, beside Download SQL, gives the database three more ways:
    • SQL, structure only — the same file with no rows: the tables, their keys and indexes, and the views, and no sequence left at the number the rows had reached.
    • Laravel migrations — a .zip to unpack in the root of a project: a file in database/migrations for each table, written with the Blueprint method that makes each column — id(), timestamps(), softDeletes(), rememberToken() where a table carries them — its indexes, and its foreign keys. A table comes after the ones it points at; the keys that close a circle are in a last migration of their own, and the views in one that runs their SQL. What Blueprint has no word for — a type of one database only, an index on an expression — is written as the nearest thing, with a comment on the line above. The migrations table is left out: Laravel makes it itself.
    • Laravel seeders — a .zip with a class in database/seeders for each table that holds rows, and DatabaseDataSeeder, which calls them in the order of the migrations: php artisan db:seed --class=DatabaseDataSeeder, on tables that are there and empty. Bytes are kept through base64, the next id is set on PostgreSQL, where the foreign keys are let wait until every table is filled, and the archive is written while it downloads, so a large table fills neither the memory nor the disk.
    The migrations and the seeders are read from Laravel's schema builder, so they are the same from every driver, and what one database wrote runs on another — but for a view and a generated column, which are the SQL of the database they came from. The downloads are at /larapilot/database-export/sql, /migrations, and /seeders, with no extension, since a web server may keep .sql and .zip for itself; /larapilot/database.sql still answers.
  • Local by default. Like the file manager, the page is open in local, development, and testing; on any other environment it is served only when dashboard_auth is YES, and answers 404 otherwise.
  • When the database cannot be read — the server is down, a value in .env is wrong — the page says so with the driver's own message. LARAPILOT_DATABASE_VIEWER_CONNECTION points it at another connection of config/database.php; LARAPILOT_DATABASE_VIEWER=false removes the page.

Logs

/larapilot/logs reads what the application wrote to storage/logs: every .log file, the one Laravel writes to now opened first. It reads, and never writes. A file has its address without the extension — /larapilot/logs/laravel for laravel.log — because a web server may refuse an address that ends in .log, or look for a file of its own there; the address with it leads to the one without.

Repeats counted: one row for each thing logged, with how many times
Repeats counted: seventy entries become one row for each thing logged — seven times the same full class, twice the same Stripe key, redacted.
  • Entries, not lines. An entry is what Laravel wrote between one [date] env.LEVEL: and the next, its stack trace included; the newest comes first. Each one shows its level and message and, for an exception, the class, where it was thrown as a file of the project — a path of the server is read as the file it is in this checkout — the exception that caused it, and the context as indented JSON.
  • Your code apart from the framework's. A stack opens on the frames of the application; the ones of the framework and the packages are one click away. A file that is in the checkout opens in the file manager.
  • By level, by period, by words. A level returns itself and every one more severe (Error and worse), and the count of each level sits above the list. The search wants every word, in any case; quotes keep a phrase together, and a class name is found with its backslashes as the page shows them or as the log doubles them. The period is the last hour, day, week, or month.
  • Repeats counted turns the list into one row for each thing logged — the same message with its numbers, ids, and quoted values taken out, or the same exception thrown from the same line — with how many times and since when, the most repeated first. Every time it was logged goes back to the entries of that one. A log where nothing repeats is counted up to 10,000 different things, and the page says so.
  • Any size. A file is read from its end backwards — 32 MB for a request (LARAPILOT_LOG_VIEWER_SCAN_MB), 50 entries to a page (LARAPILOT_LOG_VIEWER_PER_PAGE) — so the newest entries of a log of gigabytes come at once. Older and Keep reading older go further back, and a page stays the same however much is written after it. Of an entry longer than 16 KB the start is shown, with its real size. A file that is not in Laravel's format, such as the output of a worker, is read a line at a time.
  • Secrets are never shown. Passwords, tokens, keys, cookies, and Authorization headers are [REDACTED] on screen, a value written as JSON ("password":"…") included, and a search never finds one. Under such a key the value is hidden whatever it is — a string, the list a header comes as, a number — and so is the JSON of a request body logged as text; a line the redaction cannot check is hidden whole. Download log gives the file redacted the same way, with a line longer than 1 MB cut at that size; Secrets as written gives it as it is, and is offered only in local, development, and testing — on a shared host they never leave the server, sign-in or not.
  • Local by default. Like the file manager, the page is open in local, development, and testing; on any other environment it is served only when dashboard_auth is YES, and answers 404 otherwise. Only the .log files of the folder are opened, three folders deep, and a symlink is never followed. LARAPILOT_LOG_VIEWER_PATH reads another folder — absolute, or from the root of the project; LARAPILOT_LOG_VIEWER=false removes the page.
  • For the skills, php artisan larapilot:logs reads the same files — see Diagnostics and logs.

Laravel

/larapilot/laravel shows how the framework is set up in the application and what it is doing now. Five tabs; nothing of the application is changed from any of them — no cache is cleared, no job retried, no mail sent again.

The Laravel overview: drivers and caches of the running application
Overview: the drivers and caches of the running app.
The Queue tab: jobs waiting, delayed, and failed
Queue: jobs waiting, delayed, and failed on the database queue.
The Schedule tab: the tasks of the scheduler with their next run
Schedule: the tasks as schedule:list reads them.
The Dumps tab: values dumped in a controller and in a queued job
Dumps: what dump() printed in a controller and in a queued job.
  • Overview. Five numbers that lead to the other tabs — scheduled tasks, jobs waiting, failed jobs, mail kept, dumps kept — then two panels. Drivers: for the database, the cache, the session, the queue, the mail, the filesystem, broadcasting, logging, and hashing (Scout and Octane when installed), the store, connection, or mailer in use, its driver, and what says where it points — host and port, bucket, table, prefix — never a password or a key. A driver that keeps nothing is said so: with sync a job runs inside the request, with log a mail reaches nobody. Caches: whether the configuration, the routes, and the events are cached, how many views are compiled, and the cache of the application — its store, whether it answers, and how much it holds where that can be counted (file, database) — each with the artisan command that builds it and the one that empties it. On a developer's machine a cached configuration carries a warning: a change to .env is not read until it is cleared.
  • Schedule. The tasks of the scheduler as php artisan schedule:list prints them, read from routes/console.php, withSchedule(), or the console kernel: the command, the description, the cron expression, the next run, and its options — no overlap, one server, background, in maintenance, the environments it is limited to. A task that does not run in this environment is greyed. The page cannot tell whether the cron of the server calls schedule:run.
  • Queue. On the default connection — or another one of config/queue.php, a click away — how many jobs wait, are delayed, or are held by a worker, queue by queue, and the first 50 jobs in the order a worker takes them: class, queue, state, attempts, since when. The jobs are listed for the database and redis drivers — on Redis the queues are the one of the connection, the ones Horizon is told to work, and any other that holds a job; SQS, Beanstalkd, and the others give the numbers only. Failed jobs lists the last 50 with the first line of the exception, secrets redacted, and the queue:retry command for each; open batches are counted.
  • Mail. Every mail the application sends is kept and listed, the newest first: subject, recipients, the mailable or notification that built it, the request or command it was sent during. A mail opens on its headers, the names and sizes of its attachments — not the files — and the message: the HTML inside a frame that runs no script, where a link opens in a new tab, and the plain text. It listens to what Laravel says it sent, so it works with every mailer, log and array included, and changes nothing of the mail or of where it goes.
  • Dumps. What dump() and dd() printed, the newest first: the value as text, the file and line that dumped it — the Blade template, for a dump in a view — and the request or command it happened in. A dump still shows where it always did; here it stays to be read, also when it came from a job, an API call, or a response nobody saw. While Laravel Herd is watching the dumps it takes them all, and the page says so.
  • Where they are kept. The last 100 mails and the last 100 dumps (LARAPILOT_LARAVEL_VIEWER_KEEP) sit in storage/larapilot/, a folder that ignores itself in git (LARAPILOT_LARAVEL_VIEWER_PATH moves it). Forget them all empties a list. They are kept on a developer's own machine only — local and development, and not while the tests of the project run: a mail carries reset links and personal data, a dump whatever the code was holding. LARAPILOT_LARAVEL_VIEWER_MAIL=true or LARAPILOT_LARAVEL_VIEWER_DUMPS=true keeps them wherever the page is served; false never.
  • Local by default. Like the file manager, the page is open in local, development, and testing; on any other environment it is served only when dashboard_auth is YES, and answers 404 otherwise. LARAPILOT_LARAVEL_VIEWER=false removes the page.

Board

Summary metrics at the top: total specs, done count, completion rate, and WIP. A search field and priority, epic, and status filters narrow the columns; the counts follow whatever is still visible. Each workflow column lists spec cards — code, title, epic, story points, priority, subtask progress bar, mockup indicator when HTML exists, pill badges for comment and blocking counts when feedback exists, and a merge-request link when the story is DONE. That link sits beside the card target, so the board does not leave a blank card next to a merged story. Download status (.md) saves the board as it stands — a summary, specs and story points by status, then every spec with its priority, points, epic, task progress, and merge commit. Download epics (.md) saves the same stories as an outline: the project as the title, each epic with the story points of its stories, each user story with its own, and under a planned story its tasks — a task is estimated in hours, so that is what it carries. Epics and stories come in the order of their codes, the stories with no epic last. With a filter on, either file holds what is on screen and says which filter produced it; an epic then counts the points of the stories that are left.

The Board: sixteen stories by status
Fernway's board: each card with points, priority, task progress, mockup, comments, and the merge that closed it.

PRD

The living product document from /larapilot-inception (and later /larapilot-feature, /larapilot-bug, or /larapilot-prd updates). Headings become anchor links in the sidebar; the body is rendered Markdown — the same file skills write to .larapilot/docs/PRD.md. The search field finds a requirement, a persona, or a word as you type, in the PRD and nowhere else: the index is built in the browser from the headings of the document and the text under each, so the menu and the decision journal are never found. / or Ctrl/Cmd+K focuses it, the arrows and Enter move and open, a result lands on the first match of its section with every match highlighted, accents are ignored (perche finds perché), and ?q= in the address opens the page on a query. Download PRD (.md) saves the whole document exactly as it is on disk. Download functional summary produces one Markdown file in the PRD's language for someone who will never read the full PRD: the one-sentence pitch, the personas, what is in and out of this version, and every requirement as a numbered point — required first, then what matters, then what can wait. The wording of each point stays the PRD's; only the frame is translated. Below the PRD, the decision journal from .larapilot/decisions.yaml is grouped (project / discovery vs per user story) with a timeline — see Decision journal.

The PRD page: sections in the sidebar and the rendered document
The PRD of Fernway, its sections on the left and a search that looks in the PRD alone.

Inception

The discovery choices snapshot from .larapilot/choices.yaml — Project Kind, Delivery Target, Business Model, operations and support, topology, data store, and the other fixed choices inception persisted.

The Inception page: the choices discovery fixed
What discovery fixed: the kind of product, how it makes money, the stack, where it runs.

Plan

The schedule: epics, milestones, schedule criticality, and a dependency-aware Gantt with a time axis, a today marker, epic grouping, and filters for assignee, status, and task detail. Lucille's schedule notes appear when present.

The Plan: release milestones and the Gantt by epic
Milestones by release — 0.1.0 shipped, so done — and the Gantt by epic, with tasks hidden.

The Gantt is a forecast in sequence. Open work is queued from today, one spec at a time: what is in progress first, then by priority and code — the order of spec-next — and never before the specs named on its **Blocked by:** line; a spec that blocks a more urgent one is as urgent as it. A working day is 6 hours, Monday to Friday; a spec with no plan counts 4 hours for each story point. Inside a spec a task waits for its dependencies and for its assignee, so only tasks given to different people overlap. Done work sits in the past, on the days the ledger recorded for its spec. Forecast end is the day the last open spec is done. An epic deadline is measured against the last spec of the epic, a milestone that names a release (schedule-set --release=0.2.0) against the last spec of that release, and any other milestone against the forecast end. A milestone that names a release is done once that release is shipped: it is never reported overdue for a release already out. /larapilot-schedule re-plans the order and the dates.

View switches between the specs grouped by epic and one queue in delivery order. A plan wider than the page opens as the whole plan; Zoom → Detail widens the days and opens on today.

Design

Open /larapilot/design for every HTML mockup produced by /larapilot-design. The page opens on every screen, flow by flow, each one a card with a live preview, its name, and its file; the entry screen of a flow is marked. A field narrows the cards by screen or flow name. Click a card and that mockup opens as wide as the window, as a site you browse: links inside it work, and the caption, the counter, and the address follow you from screen to screen. All screens — or the browser's back button, or Esc — returns to the gallery where you left it; the arrows and the ← → keys walk the whole package in order, and the other screens of the same flow sit under the viewer. The address carries the open screen (?screen=), so a link to one screen can be shared. A mockup folder that is not a spec is named once, by its folder. Top-right Download zip packages the presentation index.html, every mockup HTML file, local assets, and referenced design-system files so the pack can be opened offline; Open index in new tab shows that cover page. When /larapilot-design mocked several visual directions (mockups/{spec}/styles/{slug}/), each style is listed on its own. Opening a screen shows a style switcher and Use this style — the choice is written to styles.yaml and logged in the decision journal, same as larapilot:mockup-choose-style.

The Design gallery of the member app mockups
Five screens of the member app, each tagged with the stories it covers.

Settings

Every project mode with its config.yaml key, current value, and what each option does — the same keys as Project settings. Read-only: change them with /larapilot-settings or larapilot:settings-set.

The Settings page: project modes with their values and options
Gitflow, release mode, comments, and code history on; every other mode with what it would do.

Skills

Every skill the agents of the project can run, whoever brought it. The page opens on who reads them here — the folder of each agent found in the project, with how many skills it holds — and then lists the skills by where they come from. A field finds one by its name, its package, or a word of what it does. Opening the page re-registers the custom skills with Boost.

The Skills page: agent folders, a custom skill, and the packaged skills
Claude Code and Cursor read the skills here; /studio-release-notes is Fernway's own, the 29 below come with Larapilot.
Group Read from What the page adds
Custom skills .larapilot/skills/ Registered / Not registered
Packaged with Larapilot The package itself What is on screen is what the agent runs
Written for Boost in .ai/ .ai/skills/, .ai/{package}/skill/ The copy Larapilot registered of a custom skill is not listed as a second skill
From other packages resources/boost/skills/ of every package in vendor/ and node_modules/ A package that came with another one says so: Boost publishes the skills of a package only when the project requires it itself
From Laravel Boost vendor/laravel/boost/.ai/ The ones an agent has come first; the ones Boost ships for packages the project does not use are folded. Of two versions under one name, the published one is read
Only in the folder of an agent .claude/skills/, .cursor/skills/, .github/skills/, .agents/skills/, and the others No package and no folder of the project accounts for it: added by hand, or by another tool

Under every skill, which agent has it: the folder of an agent holds a copy, and the page says whether that copy is the text of the source, was made by Boost from a template, or is not the same text — an old copy, which php artisan boost:update publishes again. A version of a package skill the project keeps in .ai/skills/ is named, because that is the one Boost publishes.

Click a skill to read it (/larapilot/skills/{name}). The page shows the skill as a document, not as a file: the title, the slash command, and the description on top; the front matter as facts instead of text; the Markdown rendered — headings, tables, task lists, code — with a list of its sections that marks the one being read; the files that sit beside SKILL.md; and Download SKILL.md for the file as it is on disk. A packaged skill is read from the package itself, so what is on screen is what the agent runs. Raw HTML in a skill is shown as text, never run.

When several skills carry one name, the address says which one is meant (/larapilot/skills/{name}?from=boost.tailwindcss-4) and the page lists the others. A skill written as a Blade template (SKILL.blade.php) is never run by the dashboard: it is shown as Boost published it in the folder of an agent, or as it is written when no agent has it. The name and the source are looked up in what is on disk, never turned into a path, and a link that leads out of the project is not followed.

What the agents are told

A skill runs when it is called. Under the skills, the page lists what is read before any request: CLAUDE.md, AGENTS.md, GEMINI.md, .github/copilot-instructions.md, .junie/guidelines.md, the rules in .cursor/rules/, and what the team wrote for Boost in .ai/guidelines/ and .ai/rules/. Each card says which agent reads the file and how many parts of it each author wrote.

A click opens /larapilot/skills/guidelines/{id}. Boost writes the guidelines of every package into those files between two markers, one part for each; the page cuts the file where Boost cut it and says who wrote each part:

Author How it is known
Boost A part Boost names itself: foundation, boost, php, laravel/core, pest/core, …
Larapilot andreapollastri/larapilot/core
Package A part named after a package that is installed and brings resources/boost/guidelines/
Project A part named .ai/…: the guidelines of the team
By hand What is before and after the markers. Boost keeps it; what is between them is written again at every boost:update

A file Boost did not write is read as one document, with the list of its sections and its front matter as facts.

Security

The findings of Aikido for this repository. The page is always in the menu; with aikido off it says what Aikido is and how to connect it. Full chapter: Aikido.

Errors

What the running application threw, as the tracker of the project recorded it — Boogle, Sentry, Bugsnag, Flare, Datadog, Rollbar, Honeybadger, or CloudWatch. The page is always in the menu; with errors off it says what Boogle is, how to connect it, and which other trackers can be read instead. Full chapter: Production errors.

Git

Three views of the local repository. None of them calls GitHub, GitLab, or any remote API.

The Git history graph with Gitflow branches and pull request merges
The history of Fernway: stories merged into the release branch, one branch pushed and one only on this machine.
  • Contribution calendar — every commit on every local branch for the last 12 months. The grid fills the panel width, today sits on the right, and the developer dropdown shows one author's squares.
  • Branches — every local branch with its type (main, develop, feature, bugfix, release, hotfix), its last commit, and where it stands against the branch it is heading for: work goes into the integration branch, what ships goes into the main line. The state is a sentence — 3 commits to merge into develop, 3 commits to merge, 8 behind develop, Merged into main — beside a bar of commits behind and ahead. The branch checked out is marked You are here; a branch that names a spec (feature/US-004-invoice) links to it. On the remote says whether a branch was never pushed, has commits to push or pull, or was deleted upstream. Merged branches fold into one line, because their work is already where it was going; branches that exist on the remote only are listed apart.
  • History — the last 150 commits (300 or 600 on request) drawn as a graph, newest at the top. The main line keeps the first lane and the integration branch the second, so the picture reads the same from one repository to the next. A filled dot is a commit, a hollow one a merge; a curve leaving a dot shows where a branch came from or what it merged. Labels mark the head of each branch and every tag; each row names the branch the commit was made on, its author, its date, and its hash, linked to the forge when the remote is known. With a developer selected, the commits of the others fade.

Git does not record which branch a commit was made on, so the page works it out the way a person reads a history: a branch owns what its head reaches through first parents — the main line first, then the integration branch, then releases and hotfixes, then the rest — and a branch that was merged and deleted is named from the message of the merge that closed it (Merge branch 'feature/US-002-reset' into develop, Merge pull request #12 from team/feature-x). The main line is main, master, trunk, or the default branch of origin; the integration branch is develop, development, or dev. A repository with neither is still drawn, read against the branch that is checked out.

The graph uses four colours and a grey — main line, integration, work branches, releases and hotfixes, other — checked for colour-blind separation on both themes. Colour is never the only sign: every row names its branch.

Usage

Lucille's token and hour ledger — totals, category bars, history, and a one-click Markdown report at /larapilot/usage/report.md (same data as larapilot:usage-report --insights). Deadlines and the Gantt live on Plan. Reference: Usage & Lucille.

Usage: Estimate vs build for each delivered spec
Eight delivered stories: estimated at 91.5 hours, built in about 27, one sent back once.

Economics

What the project costs, what the client pays, and what is left for you, written for someone who has never read a balance sheet — when account is FREELANCE or COMPANY. Full chapter: Economics.

Docs

The delivery loop, optional branches keyed off the current settings, every packaged skill with its primary output and lead personas, and the persona roster.

The Docs page: the delivery loop and its side paths
The delivery loop and its side paths, as the settings of the project have them.

Spec detail

Click any card to open /larapilot/specs/{code}:

A spec in review with its header, story, criteria, and mockup
US-006 in review: the header and the story line by line, as the spec template writes them, and the status the backlog holds.
  • User story — acceptance criteria and narrative from the spec YAML, each line as the spec skill wrote it — the header, the blocker, the three lines of the story — and the **Status:** of its header kept in step with the backlog at every change of status
  • Decision journal — entries for this spec from .larapilot/decisions.yaml (superseded context and regression guard)
  • Mockups — when /larapilot-design created HTML in .larapilot/mockups/{code}/, an embedded preview and screen links appear (served via /mockups/{code} in dev/staging). All mockups also live in the Design gallery at /larapilot/design
  • Internal feedback — accordion list of PM/dev comments (author, date, status, preview); blocking items show a Needs rework badge. Post new comments with a Markdown toolbar and compact footer (blocking checkbox, log path) until the story is DONE. Toggle with settings.comments / larapilot:settings-set --comments=YES|NO; LARAPILOT_COMMENTS_ENABLED=false is a deployment kill-switch — see Comments.
  • Technical plan — plan Markdown once /larapilot-plan has run
  • Tasks — expandable subtasks with status, type, body, and commit SHA when marked done

Status badge, story points, and merge commit appear in the header. Download spec (.md) (/larapilot/specs/{code}/spec.md) saves the whole spec in one file: the facts of the header, the user story, the technical plan, every task with its status, type, estimate, dependencies, commit, and body, the decision journal, and the internal feedback, oldest first, under a note to remove it before the file goes to a client. Headings inside a story, a plan, or a task are moved under the heading of the file, so the outline stays in order. The dashboard mirrors repo state — skills remain the only way to change it.

Economics

Two things wear the same name. /larapilot-economics is the skill — a conversation with Aurora that calibrates who is selling the work and then writes the client quote. /larapilot/economics is the page — an interactive pricing tool that recomputes that same maths live while you move dropdowns. The skill decides; the page explores. Both read one engine (EconomicsService::snapshot()), so they can never disagree. Everything below needs settings.account to be FREELANCE or COMPANY — on NONE the page says so and stops.

The Economics page: the short answer and the figures behind it
Fernway sold as a subscription by a UK limited company, at £49 a month.

How the skill runs. Short rounds, at most three questions each, current value always marked, skipped answers never re-asked:

  1. Country & regime — tax residency, then the regime from the FY-2026 catalogue (forfettario, ordinario, SRL, Ltd, GmbH, …). Aurora offers only regimes the catalogue actually has; it never invents a rate or a bracket.
  2. Rate & margin — hourly rate, target margin, monthly overhead, commercial discount, team size. The discount comes out of the margin, not the cost, and Aurora says once when it has priced the project under what it costs to deliver. Team size compresses the calendar and never the price.
  3. Product model — how the project makes money, the four options in the table below. If it lands on SaaS, the same round asks monthly list price, churn, planning customers, and the hosting baseline.
  4. Market research — Jennifer (positioning) and Benjamin (market) go and find named competitors with their real list prices, the price trend, demand scenarios, and packaging tiers, and persist the result with larapilot:economics-market-write. Offered by default for subscription and product work; on a one-off client delivery it is skipped unless you ask — the page says as much under No competitor data. Nothing here is invented: an unknown price is left out rather than guessed, and every figure carries its source URL.
  5. Present — Aurora reads the snapshot back in a fixed order: quote, net to owner with the effective tax rate, where the hours come from plus any warnings, and — for SaaS — the three price lines and the three business-plan readings.
  6. Write the client quote — a commercial document, in the PRD's own language, any language, through larapilot:economics-quote-write.

The skill touches nothing else: not the PRD, not the backlog, not the code. It writes the economics profile, the market research, and the quote document, always through the CLI — never by editing economics.yaml by hand.

What you end up with.

Artifact Where What it holds
Account profile .larapilot/economics.yaml Country, regime, rate, margin, discount, team size, overhead, maintenance %, product model, subscription inputs. Written only by larapilot:economics-set.
Computed snapshot .larapilot/economics.snapshot.yaml The whole quote as the engine computed it — effort per spec, tax, payback, packaging, business plan, market. Refreshes itself whenever specs, plans, the PRD, or inception change, so the cost board follows the backlog with no manual trigger.
Market research .larapilot/economics.market.yaml Sector, segment, named competitors with price and trend, demand scenarios, tiers, risks, sources. Written by Jennifer and Benjamin, plotted by Larapilot, invented by nobody.
Client quote .larapilot/docs/quote.md The commercial document you send: offer summary, objectives, what the client gets, infrastructure and hosting, security and quality, investment table, timeline, payment milestones, what is and is not included, signatures. No story points, no tax breakdown, no architecture. Download it at /larapilot/economics/quote.md; convert with pandoc quote.md -o quote.pdf. Until one is written, a built-in en · it · es · fr · de · pt · nl · pl template serves the download.
Internal report /larapilot/economics/report.md The engineering-side read: taxable base, contributions, extraction, scenarios. Never shown to the client.
JSON /larapilot/api/economics The same snapshot for an agent, and it accepts the same what-if parameters as the page (?hourly_rate=70&tier=premium&discount_pct=10).

The page is written for someone who has never read a balance sheet. Three rules hold from top to bottom: the answer comes before the detail, no figure appears without the sum that produced it, and a word of finance appears only in the glossary, next to the plain words the rest of the page uses for it.

Block What it answers How it shows it
The short answer What happens, in one sentence The sentence, then four figures. A one-off build: what the client pays, what you keep (and how much of every 100 of the price that is), the time to deliver, the upkeep each year. A subscription: the cost to build, what one customer leaves, the customers that cover the monthly bills, and the month the build is paid back — or not within 3 years
Try other numbers What if the rate, the margin, the price were different The console of dropdowns. Pointing at a control, or reaching it with Tab, writes what it changes under the console. A fold you opened stays open when a control redraws the page
The subscription, one step at a time
SaaS
Is a subscription worth it Five numbered steps: one customer, one month (price − card fees − support = what one customer leaves), what the product costs every month (hosting + keeping the code healthy + the share of your running costs), how many customers it takes with both divisions written out, when the money comes back, and the three forecasts side by side
What the client pays and what you keep How the price is built, and where it ends up Two receipts, each under a bar drawn to scale. How the price is built: work + running costs + margin (− discount) = client price, + VAT = what the client pays. Where the money goes: client price − running costs − tax and contributions − accountant (− what the law keeps in a company) = what you keep. Then the same thing in one sentence: out of every EUR 100 of the price, EUR 71 stay with you…, and what that makes for each hour of work
After delivery What recurs once the build is paid The maintenance retainer and what it is priced on, the share of your working year the project takes, and — by model — the orders or the licences that repay the build
Packaging SaaS What three prices would do BASE, PRO, and PREMIUM side by side, each with what one customer leaves and the customers it takes at that price
Where the hours come from What the price rests on Hours by release or by epic as bars, then every user story with the source of its hours. Once a spec is delivered, one line for you alone: the hours the quote counts for the delivered specs and the time the agent took to build them — never in the client quote
The market Where the price sits Researched competitors, the price trend, and your position against the median
Words used here What the terms mean VAT, margin, net, fixed costs, MRR / ARR, churn, contribution, break-even, LTV : CAC, person-months — each defined in one or two sentences, with the figure it has in this project

When the money comes back is a chart of what the product has earned so far, everything counted. The line starts below zero by the cost of the build, each month adds what the subscriptions leave after the bills and tax, and where it crosses zero the product has paid for itself. The forecast in use is the coloured line; the other two are grey. Moving over the chart — or the arrow keys, Home, End, Page Up, Page Down once it has the focus — reads any month: customers, what they paid, what was left, the total so far. Under the chart the same story is told in sentences: Launch — you are EUR 5,129 down, Month 20 — the subscriptions cover the monthly bills, After 3 years — there are still EUR 4,218 to earn back. Month by month holds the table. Each forecast carries a verdict in words: pays for itself in month 24, covers its bills from month 20, not the build yet, or loses money every month.

The bars use two sets of three colours — what a price is made of (work, costs, margin) and where it ends up (kept, tax, costs) — checked for colour-blind separation on both themes, with costs the same colour in both. A figure is always written beside its colour, and a verdict is an icon and a sentence, never a colour alone. The page reads in the PRD's language (en · it · es · fr · de · pt · nl · pl, English otherwise), like the client quote; the console stays in English on purpose, because its labels and the economics-set command it prints are operator controls.

Eight languages, all or nothing. Detection (ArtifactLanguage) and the three places Larapilot writes prose — the Economics page and engine, the client quote, and the design presentation — are kept in step by a test: a language listed as supported but missing from one of them fails the build. That is deliberate. A half-translated language renders English in one artifact and not the others, which is worse than not offering it. Agent-written documents are not limited to this set: /larapilot-economics writes the client quote in whatever language the PRD uses, and the built-in template is only the fallback for projects where nobody wrote one yet.
Nothing is written from the browser. Moving a dropdown is a simulation: the page recomputes server side through the same snapshot the CLI uses and prints the exact php artisan larapilot:economics-set … command that would make it the real profile. Out-of-range values are dropped rather than clamped, so an edited URL cannot push a figure the engine would refuse.

Sold as — the four ways a project makes money

The Sold as toggle decides which half of the page is the real one. It never changes the hours or the build price — those come from the backlog either way. It changes the question the page answers about getting the money back.

Sold as What it means What the page computes for it
One shot — fixed price
fixed
The client pays once for the build. After go-live only the maintenance retainer recurs. The classic agency or freelance commessa. Payback read as capacity: what share of your working year the project takes, how many projects like it fit in a year, and what a year filled with work priced this way would leave you after tax. No packaging, no subscription forecast. Market research is skipped unless you ask for it.
SaaS — subscription
saas
The build is money you put in and earn back from monthly subscriptions. The client is the market, not one buyer. The whole second half of the page turns on: BASE / PRO / PREMIUM plans cut out of the backlog, contribution per customer after payment fees and support, break-even customers, a 36-month business plan read pessimistic / realistic / optimistic, hosting costs, LTV:CAC — plus the Subscription group in the console (list price, price line, scenario, churn, growth, planning customers). Market research is offered by default, and researched competitors are plotted against your price.
E-commerce
ecommerce
Priced like a one-off build — you invoice the shop, you do not sell subscriptions — but the shop earns its build back on order volume. The one-shot price, plus payback read in orders: how many orders a month the shop has to take to repay the build inside a year. The average order value and the take rate behind that figure are engine assumptions, not researched numbers, and the page says so — read it as an order of magnitude, not a forecast.
Licensed package
package
Priced like a one-off build, then repaid by many licences instead of one client — a product, a plugin, a licensed library. The one-shot price, plus a suggested annual licence price and how many licences repay the build. It uses the annual price on your profile when there is one; otherwise it suggests a fraction of the build and tells you that is what it did.

Left on its default (auto), the model is inferred from the Business Model answer at inception, then from the PRD. The dropdown always wins over both, and the same four values are accepted by larapilot:economics-set --product-model=. Every one of them is explained in place on the page, under What “Sold as” changes.

API

JSON API over the same artifacts as the dashboard. Read endpoints for scripts, CI dashboards, external frontend repo setup, bug-triage diagnostics, or tooling; POST /specs/{code}/comments appends internal feedback when enabled. Workflow state still changes only via skills/CLI.

Activation

API routes share the dashboard gate: enabled when dashboard activation conditions are met. Base path: /{prefix}/api (default /larapilot/api). Returns 404 in production or when disabled.

Authentication

Set LARAPILOT_API_TOKEN to require a shared token on every API request — send it as Authorization: Bearer <token> or an X-Larapilot-Token header. When the token is set it is enforced on every endpoint, reads and writes alike. Without a token configured, read endpoints stay open in the allowed environments, but write endpoints (POST /comments) are refused outside local/development/testing.

The api_auth project setting (default NO) makes the token mandatory: with api_auth=YES every /larapilot/api/* call — the /diagnostics, /openapi.json, and /docs endpoints included — requires LARAPILOT_API_TOKEN, and the API fails closed (HTTP 503) when the env var is not set instead of answering unauthenticated. Turn it on with php artisan larapilot:settings-set --api-auth=YES. Client setup and curl / Laravel HTTP / fetch / CI examples: .larapilot/integrations.md → API access.

The optional dashboard_auth setting (Basic Auth on the dashboard UI) does not apply here, and api_auth does not apply to the dashboard UI — the JSON API is gated only by LARAPILOT_API_TOKEN / api_auth and the dashboard activation conditions.

Calling the API with the token
curl -sS -H "Authorization: Bearer $LARAPILOT_API_TOKEN" \
  "$LARAPILOT_BASE_URL/larapilot/api/board"

# Laravel HTTP client
Http::baseUrl(env('LARAPILOT_BASE_URL').'/larapilot/api')
    ->withToken(env('LARAPILOT_API_TOKEN'))
    ->get('/diagnostics', ['no_logs' => 1])
    ->throw()->json();

Endpoints

Method Path Returns
GET /larapilot/api/board Metrics, status order, and specs grouped by workflow column — each spec includes mockups.screens (absolute preview URLs) and a counts-only feedback summary
GET /larapilot/api/specs Specs with task progress, mockup screens, and counts-only feedback summaries. Optional ?status=TODO filter; paginated with ?page / ?per_page (1–200, default 50) — body carries total, page, per_page, total_pages
GET /larapilot/api/specs/{code} Single spec with plan body, tasks, workdir, task progress; each spec embeds mockups.screens (absolute preview URLs) and feedback.entries
POST /larapilot/api/specs/{code}/comments Append internal feedback — JSON body: author, message, optional blocks_merge. Returns 201 with updated feedback snapshot.
GET /larapilot/api/prd PRD Markdown content and parsed heading index
GET /larapilot/api/metrics Delivery snapshot — backlog completion, plan/task progress, (when the Lucille ledger is on) an effort-timing block: tracked hours, tokens, first/last activity, and build: the hours the delivered specs were estimated at beside the time they spent IN PROGRESS
GET /larapilot/api/economics Economics snapshot — quote, tax, payback, packaging, business plan, market (enabled: false when account is NONE). Accepts what-if query parameters (?hourly_rate=70&discount_pct=10&tier=premium) that are computed and returned, never stored
GET /larapilot/api/backstage Backstage catalog entities, rendered catalog-info.yaml, TechDocs metadata, and a lean delivery snapshot for a portal plugin. Full guide: Backstage portal.
GET /larapilot/api/backstage/catalog-info.yaml The same entities as a multi-document YAML descriptor — consumable as a Backstage url location
GET /larapilot/api/diagnostics Read-only runtime snapshot for bug triage — see Diagnostics
GET /larapilot/api/openapi.json Machine-readable OpenAPI 3 document (auto-generated)
GET /larapilot/api/docs Swagger UI — try requests in the browser
The API docs page: Swagger UI over the JSON API of the dashboard
/larapilot/api/docs on Fernway: every endpoint of the JSON API, to try in the browser.

Response shape

Plain JSON — not the larapilot/v1 CLI envelope. Example from /larapilot/api/board:

GET /larapilot/api/board
{
  "metrics": { "total": 3, "done": 1, "completion_rate": 33.3, "wip": 1 },
  "status_order": ["TODO", "PLANNED", "IN PROGRESS", "REVIEW", "DONE"],
  "columns": {
    "TODO": [{
      "code": "US-002",
      "title": "Project Management",
      "task_progress": { "total": 0, "done": 0 },
      "mockups": { "available": false, "screen_count": 0, "screens": [] },
      "feedback": { "enabled": true, "available": false, "entry_count": 0, "blocking_count": 0, "writable": true, "entries": [] }
    }],
    "DONE": [{
      "code": "US-001",
      "title": "User Registration",
      "task_progress": { "total": 4, "done": 4 },
      "mockups": {
        "available": true,
        "screen_count": 2,
        "entry_url": "https://app.test/mockups/US-001",
        "screens": [
          { "file": "index.html", "label": "Index", "url": "https://app.test/mockups/US-001" },
          { "file": "desktop.html", "label": "Desktop", "url": "https://app.test/mockups/US-001/desktop.html" }
        ]
      },
      "feedback": {
        "enabled": true,
        "entry_count": 1,
        "blocking_count": 0,
        "writable": true,
        "entries": [
          { "at": "2026-07-15 17:00", "author": "PM", "status": "REVIEW", "body": "Looks good.", "blocks_merge": false }
        ]
      }
    }]
  },
  "workflow": { "todo": "TODO", "planned": "PLANNED", "in_progress": "IN PROGRESS", "review": "REVIEW", "done": "DONE" }
}

The API is a development and staging tool: it is never registered in production, and on a shared host it belongs behind LARAPILOT_API_TOKEN with api_auth=YES. Mockup preview URLs are absolute in API responses when the mockup route is browsable. Comment writes need comments=YES, honor LARAPILOT_COMMENTS_ENABLED, and reject DONE specs with 422. Diagnostics honor LARAPILOT_DIAGNOSTICS_ENABLED and redact secrets in log tails.

API hardening

Four protections that are always on for /larapilot/api/*, whatever api_auth says.

  • Rate limit — per IP, from larapilot.api.rate_limit ("max,minutes", default 120,1). Over budget → HTTP 429 with Retry-After. LARAPILOT_API_RATE_LIMIT=0 disables it.
  • Audit log — every mutating request (POST /specs/{code}/comments) appends one JSON line to .larapilot/api-audit.log (timestamp, method, path, IP, whether a token was sent, status — never bodies). Git-ignored automatically. LARAPILOT_API_AUDIT=false to turn off.
  • Conditional requests — GET /board, /specs, /specs/{code}, /prd, /metrics return an ETag; send it back as If-None-Match for a 304 Not Modified. Ideal for pollers.
  • Security headers — every dashboard and API response carries X-Content-Type-Options: nosniff and Referrer-Policy: no-referrer; dashboard pages also send X-Frame-Options: DENY.

Diagnostics and logs

What the running application says about itself, for /larapilot-bug, /larapilot-error, and local debugging: the logs read as entries, and the health of the runtime. Both only read, and both redact secrets. LARAPILOT_DIAGNOSTICS_ENABLED=false turns both off.

Logs

php artisan larapilot:logs reads the log files of the application the way the Logs page of the dashboard does, and answers with what an agent needs: when, what, the exception and where it was thrown, and the frames of the application. /larapilot-bug runs it every time, whatever the report says; /larapilot-error runs it for every group it hands to triage.

Option What it does
--group One row for each thing logged, with how many times and since when — the most repeated first. Without it the entries come one by one, the newest first
--level=warning That level and every one more severe: debug, info, notice, warning, error, critical, alert, emergency
--search="…" Every word must be in the entry, in any case; quotes keep a phrase together. A value that is redacted is never found
--since=7d Minutes, hours, or days back (30m, 1h, 7d), or a date (2026-10-02)
--limit= 20 by default, 100 at most
--files · --file= The log files there are · another one than the file the application writes to now. A file that is not in Laravel's format has no levels and no dates: --level and --since are left out, and the answer says so

The answer opens with what the file holds — the count of each level and the period it covers — and says whole_file: false when the file is larger than LARAPILOT_LOG_VIEWER_SCAN_MB (32) and only its end was read. With no log, or nothing that matches, it answers with an empty list: nothing logged is a finding too. Through MCP it is RunArtisanTool → larapilot:logs. The command needs no token and is not gated by the dashboard; it reads the folder of the Logs page (LARAPILOT_LOG_VIEWER_PATH).

Health of the runtime

App status and health checks, with a tail of the log. Same dashboard gate as the rest of the API (never in production); LARAPILOT_DIAGNOSTICS_ENABLED=false returns 404.

Surface Usage
API GET /larapilot/api/diagnostics — optional ?lines=100, ?no_logs=1
CLI php artisan larapilot:diagnostics — --lines=, --no-logs
MCP Larapilot diagnostics tool, or RunArtisanTool → larapilot:diagnostics

Checks: storage_writable, cache, database, queue, log_file. healthy is true when critical checks (storage + database) pass. Log lines redact secrets ([REDACTED]). Defaults: LARAPILOT_DIAGNOSTICS_LOG_LINES=100, LARAPILOT_DIAGNOSTICS_MAX_LOG_LINES=500.

GET /larapilot/api/diagnostics?no_logs=1
{
  "collected_at": "2026-07-24T16:00:00+00:00",
  "app": {
    "name": "My App",
    "env": "local",
    "debug": true,
    "laravel_version": "13.x",
    "php_version": "8.4.0"
  },
  "checks": {
    "storage_writable": { "ok": true, "detail": "…" },
    "cache": { "ok": true, "detail": "…" },
    "database": { "ok": true, "detail": "…" },
    "queue": { "ok": true, "detail": "…" },
    "log_file": { "ok": true, "detail": "…" }
  },
  "healthy": true
}

Ready to ship with AI?

Install Larapilot and give your agent a real product process.

Get started →