astro2006/laravel-audit
A lightweight Laravel package by astro200 that integrates seamlessly with the framework, offering simple, focused features with minimal configuration.
Install and publish.
composer require --dev <vendor>/laravel-audit # package name per its composer.json
php artisan vendor:publish --tag=audit-config # writes config/audit.php
php artisan audit:install --yes # non-interactive; registers Boost skill + guideline
Without --yes, audit:install goes through boost:update --discover interactively. With --yes it edits boost.json directly. Either way it registers two things: the resources/boost/guidelines/core.blade.php guideline and the laravel-audit skill, which teaches your own Claude Code/OpenCode session how to drive the audit through php artisan audit:tool.
Look here first, in this order:
config/audit.php — every task has a stage: analyse, standards (default if omitted) or security. This is the map of what the tool will actually do to your repo.php artisan audit:tool catalog — prints the tasks grouped by stage, so you can see the real inventory without reading the config.docs/MVP3.md — the concept and measurement notes behind the four-stage model.First run (smallest useful thing):
php artisan audit --stage=security
This runs the model check, the preflight, then only the security-stage tasks, and writes reports under storage/audit/<run>/. Once that reads sensibly, run the whole thing:
php artisan audit
The interactive console will offer the models your CLI knows if the model check fails (Claude Code aliases, or whatever opencode models lists), and will offer to wait out a rate limit and continue in-process.
Know your exit codes early. 0 = reports written. 4 = paused, not failed: the run stopped starting tasks, left the rest pending, wrote storage/audit/<run>/tool/pause.json, and printed the resume command. Wire CI to treat 4 as "retry after reset", not "broken build".
Run the stages as separate, budgetable units. start --stage=<stage> runs every task of that stage that hasn't run yet. This is the pattern for large or expensive repos: get standards green cheaply, then spend on security.
php artisan audit:tool open
php artisan audit:tool start --stage=analyse
php artisan audit:tool status
Follow the next field instead of hardcoding a sequence. Every audit:tool answer carries next, the command the harness would issue now. That's the intended integration point — build your wrapper script around next, not around a fixed command list. This matters because the harness gates start on budget, attempt limits, pause state and reset times.
Use wait for streaming, status for the table. wait returns only finished_since / failed_since since the previous wait — it's a delta. status returns the full table plus spend_usd and pending. So: wait in a polling loop, status when a human asks "where are we, and what have we spent?"
Drive a run from your own agent session. This is the headline workflow. The laravel-audit skill plus php artisan audit:tool gives an agent exactly one tool, and the skill prompt tells it to pick tasks stage by stage, rerun failures and finish. start <task>:skill,skill pins explicit project skills to a task.
Or hand the whole loop to the headless orchestrator.
php artisan audit:orchestrate --audits=security --model=claude-sonnet-4-5 --force
It opens a run, starts one agent session with the laravel-audit skill as the prompt and audit:tool as its only tool, and bounds the session with agent.orchestrator.max_turns (300), max_budget_usd and timeout (7200 s). The closing message lands in storage/audit/<run>/orchestrator.md. If the session dies without calling finish, the command writes the reports itself — so a crashed orchestrator still leaves you with output.
Resume rather than restart. --resume continues the latest run; --resume=<run> targets a specific one. Completed task results are preserved (the CLI writes outcomes to the same storage/audit/<run>/tool/tasks/ store audit:tool reads), everything else reruns, then reports are written. A rate-limit pause followed by audit --resume is the normal happy path on a big codebase.
Budget in two places. agent.run_budget_usd (AUDIT_RUN_BUDGET_USD) pauses before a task that would push the run over. tool.max_calls (400) caps total audit:tool calls per run — past it, only status, wait and finish work. Set the USD budget for humans, and treat the call cap as the guardrail for agent loops that thrash.
Feed it project context so severity means something. project.context (AUDIT_PROJECT_CONTEXT) is free text about who uses the app and how it runs; it's injected as ## Project context alongside runtime facts from artisan about (Laravel/PHP versions, database, cache, queue, session, broadcasting, mail drivers) plus an instruction to rate exploitability against it. This is the single highest-leverage config change for signal quality.
Tune verbosity per task, not globally. A task can set max_turns in audit.php; backend.dead-code gets 30 and backend.database 25 out of the box because their prompts need room to search. Follow the same instinct when you add tasks.
Standards are Boost's own guidelines. The harness reads the rule blocks Boost wrote into AGENTS.md (or CLAUDE.md), drops the tooling blocks (foundation, boost, herd, deployments, tests, and the audit's own), appends .ai/rules and .ai/guidelines, and inlines the result as ## Coding standards into standards-stage tasks and the conventions baseline, capped at standards_max_chars (6000). agent.standards is boost (default), none, or a list of files.
Read the report from the top. Findings are numbered by severity, then confidence, then task, and task-local IDs are remapped across every section a task wrote, so a Related reference and the finding body agree. The ## Checks section lists every check with its task and result (clean, N findings (IDs), not audited, skipped) — that's your completeness audit, and the fastest way to spot a stage that silently didn't run.
Config keys that were renamed or removed will not fail loudly. v0.2.0 reports stale keys as "no longer read" rather than erroring — so a run can quietly ignore your intent. Check for these explicitly:
| Old | New |
|---|---|
agent.mode, agent.concurrency, --mode |
agent.max_concurrency (default 1), --concurrency |
agent.verify.always_categories |
gone; replaced by agent.verify.from (default medium) |
orchestrator => agent, --orchestrate |
removed; use the audit:orchestrate command |
agent.drivers.codex |
removed — the Codex driver is gone for now |
The Codex removal is worth understanding rather than just patching around: it worked through a shell and couldn't keep agents away from .env. Keep that constraint in mind before wiring in any driver that shells out.
start will refuse, and that's usually correct. It refuses until a known reset time has passed (status and wait show the pause), and a task can only be started agent.max_attempts (2) times per run. If you're scripting retries, handle the refusal as a state, not as an error to retry in a tight loop.
Don't call wait twice and expect history. It's a delta since the last wait. A second call will legitimately report nothing finished even though eleven tasks are done. Poll with wait, snapshot with status.
The model check costs a turn before the preflight. It runs on audit, audit --resume, and audit:orchestrate, and a model that doesn't answer kills the run before spend — good. But note two behaviours: a usage limit still counts as an answer (so the run pauses as normal rather than failing the check), and audit:orchestrate also checks agent.orchestrator.model when it differs from the main model. Set agent.check_model=false only if you've already validated the model in that environment.
standards_max_chars truncates silently. At 6000 chars, a repo with a fat AGENTS.md plus a full .ai/rules and .ai/guidelines can lose rules off the end. If your standards-stage findings look like they ignored a convention, check what survived truncation before blaming the model.
Be careful how you write project.context. This is the documented failure mode from the second homemanager run: the context rule used a single-user app as its example, and Haiku took the example as a fact and rated four findings down with it. Write statements about your app, not illustrative scenarios. And note the follow-on: without a project.context, tasks are told not to guess the user model and to name the assumption a severity depends on — so an empty context is safer than a misleading one.
Verify coverage got wider, and it costs. The verify pass now checks every finding from medium up unless it's confirmed, in every category — always_categories is gone. The upside is that medium/likely findings with a wrong fix no longer slip through. The cost is more verify calls, each capped at agent.verify_max_turns (6) tool calls. If spend jumps after upgrading, agent.verify.from is the dial.
Concurrency defaults to 1. --concurrency is available, but raising it against a rate-limited agent CLI is how you get exit code 4 mid-run. Scale up only after you've measured the pause rate at 1.
Skills get resolved once and cached. One agent call assigns up to skills_per_task skills based on fit, and the choice is reused while skills and tasks are unchanged. If you add a skill or a task and the assignment looks stale, --reselect-skills is the fix. agent.skills = none disables it; a map fixes it explicitly — worth doing for reproducible CI runs.
storage/audit/ accumulates. Every run keeps tool/tasks/, run.md, pause.json (when paused) and orchestrator.md (when orchestrated). Nothing suggests pruning, so add your own retention or you'll be diffing runs forever.
Report noise has been engineered down — don't fight it. Limitations are merged (preflight-is-fine bullets dropped, repeats removed, max three per task, each prefixed with its task), out-of-scope observations that duplicate a finding's location are dropped and capped at five, and titles over 120 chars cut at a word boundary. If a section looks thin, it's often intentional deduplication rather than a task that failed to produce output — cross-check against ## Checks.
Extension point: task definition. Adding a task means choosing a stage (defaults to standards, which is the wrong default for anything analytical) and possibly a max_turns. Prefer security for anything touching auth, input handling or dependencies so it lands in the right stage of a staged run and appears correctly in the summary, which names the security findings by ID.
For contributors: an arch test enforces every source file at ≤250 lines, and AgentRunner is split into the loop, JobQueue (critical-first order, readiness) and FollowUp (repair, answer-now, open-files decisions). New behaviour belongs in one of those three, not back in the runner.
How can I help you explore Laravel packages today?