Skip to main content
Commands that analyze a build accept: This keeps monorepo usage explicit: point --cwd at the app, while snapshots and budgets are stored at the detected workspace root.

init

Set up crust in a project: first snapshot, starter budgets, CI configuration.
Seven steps, each reporting what it found and what it decided:
  1. Detects the Next.js app. The directory --cwd points at wins. In a monorepo with several apps and no pointer, init lists them and stops rather than analyzing the wrong product.
  2. Checks for a production build, naming the build command for the detected package manager if there isn’t one.
  3. Analyzes it and records the first snapshot, with route count and analysis confidence.
  4. Reports whether source-map attribution is available, and what is still measured without it.
  5. Writes .perf/budgets.json — see starter budgets below.
  6. Generates CI configuration for the detected provider, pinned to the crust version that wrote it.
  7. States the three rules that need no budgets at all.
Exits non-zero when a step fails. Existing files are never replaced without --force; init reports them as kept.

Starter budgets

Generated budgets are derived from one build, so the file carries a "//" array explaining every number and separating what was measured from what crust chose:
.perf/budgets.json
readBudgets ignores the key. A shell floor appears only when a route in this build emitted a shell crust could measure; no per-route ceilings are generated, because crust cannot know which routes deserve a tighter number than the rest.
.perf/ holds generated snapshots, the mutable findings.jsonl trust log, and reviewed project configuration such as budgets. Snapshots and findings travel on the perf-history branch; the budget file is a decision worth reviewing in the application branch. init suggests the ignore rule that separates them—.perf/* with !.perf/budgets.json.

analyze

Analyze a production build and record a snapshot.
By default, analyze compares against the newest local snapshot with the same snapshot schema, bundler, Next.js major, and at least one matching route identity. Its concise view explains this build: build health, measurable analysis confidence, actionable regressions, the largest first-party contributor, shared causes, and trimmed cause chains. --verbose expands the coverage denominators and unresolved evidence. Use diff [base] [head] for the decision-first comparison surface. It can compare the current .next build with one stored baseline or resolve both sides independently from stored snapshots.

diff

Compare two builds. Exits non-zero if either side has no stored snapshot, or if --build could not produce one.
Each side accepts a build id, a git SHA, a tag, a branch name, or HEAD~3. base defaults to HEAD~1. With one argument the head is the build in .next, and a branch base resolves to its merge base with HEAD - the head under review is this working copy, so the point the two share is what the change should be blamed against. With two arguments nothing is rebuilt and neither side has anything to do with what is checked out: each ref resolves to the snapshot at that ref, or the newest one its own ancestry can reach. That is what makes crust diff v1.2.0 release/next answer about those two builds while you sit on a third branch.

Build both refs first

--build fills the gap two named refs leave: someone has to have measured them. It records each ref in its own detached git worktree and then runs the comparison above on the two snapshots it wrote.
Quote a command with arguments: --build 'pnpm --filter web build'. In a monorepo, point --cwd at the package: crust diff develop feature --build --cwd apps/web. Your checkout is never touched. The worktrees are detached at each tip, live under the system temp directory, and are removed whether the build succeeds or fails - so this works from a dirty working tree on an unrelated branch, which is the situation it exists for. Both snapshots are written to the project’s single .perf/, exactly where crust analyze on each ref would have put them, so the next comparison of the same two tips rebuilds nothing. A tip that already has a comparable snapshot is not rebuilt. “Comparable” is checked as a pair: two snapshots at the right commits that cannot be compared to each other - a bundler swap, a Next major, or another package in the same monorepo store - are rebuilt rather than reported as CANNOT COMPARE THESE BUILDS. Dependencies come from this checkout: crust links its node_modules into each worktree. When the ref’s lockfile differs from the installed one it refuses instead, because the bundle that install produces is not the one that ref describes - build with an install step to measure it:
A worktree contains committed files only. Untracked build inputs - .env.local above all - are not in it; export the variables in your shell, or set them in the build command. A failed build or analysis names the ref it failed on and exits non-zero. It never falls through to a comparison of one side against an older snapshot.

Output

Output leads with the decision (BLOCK, REVIEW, CLEAR, CANNOT DECIDE), the changes behind it - regressions and improvements together, each with the strongest source location and the likely next action - the cause those changes share, and the attribution the comparison rests on. The route table and per-route detail follow. diff reads .perf/budgets.json so the decision it prints is the one ci would reach on the same pair. It does not enforce: the exit code is non-zero only when a side has no stored snapshot. Material configuration changes that make the builds unsafe to compare appear as comparison warnings and suppress enforceable route-level conclusions.

ci

Check automatic regressions and configured budgets, then print a PR comment. Exits non-zero on a breach.
Defaults to comparing against main. Branch refs resolve to their merge-base with HEAD, not the latest snapshot at the branch tip. Without .perf/budgets.json, rendering-mode drops, newly uncached reads, and shell disappearance still fail when the baseline is comparable. A budget file adds byte, growth, and shell-ratio ceilings. See regressions and budgets. Every ci run records the current build before exiting, including runs that find a breach. If no baseline exists, crust warns that regressions cannot be detected; absolute ceilings still apply. Blocking breaches are also appended to .perf/findings.jsonl so agreement can be measured over time — see findings.

findings

Record and score whether authors agreed that a blocking finding was a real regression.
ci writes one row per blocking breach. Each row has a fresh occurrence id, which is what you mark, and a content key for identical breaches. Mode and cache failures retain that key across runs; byte, growth, and shell checks can receive a new key when the measured values in their message change. rate reports disputed / (agreed + disputed) and leaves the rate unmeasured until something has been marked—an empty log is not a 0% disagreement rate. Target before ownership routing ships: fewer than 1 in 10 resolved findings disputed.

report

Write a self-contained HTML report.
No external requests: styles, script and data are inlined. The report opens with Fix first — the same ranked findings analyze prints, worst first, each with its evidence and a concrete next step — then shared-cause blast radius, then the route table. A jump bar names the sections the report actually has; its Search routes control focuses the table’s search box. Findings and shared causes each show the three worst and fold the rest behind a disclosure that says how many there are. The fold is a native <details>, so browser find-in-page opens it to reveal a match. The report also supports:
  • search across route patterns, components, and source files
  • filters for dynamic, partially static, heavy, and unattributed routes
  • grouping by layout or workspace package
  • full cause and import chains with evidence labels
  • shared-cause blast radius that filters the affected routes
  • PR-ready shared-cause explanations
--out is resolved from the shell’s current directory, not from --cwd. In a monorepo, either run the command from the app directory or pass an explicit path such as --out apps/web/crust-report.html.

mcp

Serve this project’s stored snapshots to an MCP-capable agent over stdio.
Register it once with the agent you already use:
Eight read-only tools: list_builds, build_summary, route_detail, explain_route_cause, compare_builds, cause_blast_radius, route_history, build_findings. See Ask an agent for what each answers and the guarantees they hold to.
No tool builds, installs, or writes a snapshot, so an agent cannot publish a baseline or start a production build. Answers only cover commits someone has already run analyze on.
The tool names, inputs and response shapes are not covered by the CLI stability rules yet — see COMPATIBILITY.md.

ask

Run one of those tools here and print its answer. No agent, no client, no registration.
Arguments are key=value rather than flags, so a tool can gain an argument without colliding with --cwd or a future crust option. Output is the same JSON the agent receives. A refusal exits 1. Use it to check an answer the agent gave you, or to see the evidence without wiring anything up.

manifest

Write the snapshot where the in-app panel can fetch it.
The manifest lists every route, source path and component name in your app. Generate it in analyze builds only.

history

Sync snapshots with the orphan perf-history branch.
push reports and exits cleanly when the remote rejects it - a fork PR’s token is read-only on the base repository, and that is not a failure worth breaking the check over.

prune

Apply the retention ladder.
Newest 50 builds at full fidelity → one per commit → module detail dropped after 90 days. Route totals and shell ratios are kept forever.

synthetic

Measure routes against a running deployment. Needs Playwright as an optional peer.

list

List stored snapshots, newest first.

Exit codes

crust ci exits with 1 when an automatic regression or configured ceiling is breached. crust diff exits with 1 when it cannot find the requested baseline, and with --build when a build or an analysis of either ref failed; a displayed regression does not make diff a CI gate. Other successful commands exit with 0. Any unsupported project - Next outside 15–16, a dev-only build, a Pages Router app - throws with a message naming the reason rather than emitting numbers it cannot stand behind.