CodeVitals tracks performance metrics for code repositories. Your CI reports a set of values for each commit, and CodeVitals charts them over time, flags regressions and improvements, and can share a repository's dashboard publicly.
- Metrics over time: Charts for each metric, with zoom and a choice of how much history to show
- Regression detection: Flags changes above 10%, with an optional per-metric noise floor
- Baseline normalization: Compares runs fairly across machines by reporting a base commit alongside each result
- Public dashboards: Share a repository's metrics without requiring sign-in
- MCP server: Ask an AI assistant about any project with a public dashboard
- GitHub and custom repositories: Track a GitHub repository, or any other repository by URL
- Frontend: React, TypeScript, Vite, @wordpress/components, Chart.js
- Backend: Spacefast Functions, MariaDB
- Authentication: GitHub App (OAuth flow)
Get up and running in under 2 minutes with seeded test data:
git clone https://github.com/Automattic/gitaudit.git
cd gitaudit
./scripts/setup.sh
npm run devOpen http://localhost:3000 to see the public dashboard for WordPress/gutenberg.
The setup script also prints a sign-in link for a test user, which opens the signed-in pages without any GitHub credentials. Print it again at any time with npm run seed.
- Node.js 22+ and npm
- Docker, for the local database
- A GitHub App, if you want to sign in with GitHub rather than as the test user
- Go to GitHub Settings → Developer settings → GitHub Apps → New GitHub App
- Fill in the details:
- GitHub App name: CodeVitals (or your preferred name)
- Homepage URL:
http://localhost:3000 - Callback URL:
http://localhost:3001/auth/github/callback(add one per environment) - Expire user authorization tokens: UNCHECK this for non-expiring tokens
- Where can this GitHub App be installed?: Any account
- Set permissions:
- Repository → Contents: Read
- Repository → Metadata: Read (required)
- Organization → Members: Read
- Account → Email addresses: Read
- Click "Create GitHub App"
- Generate a client secret
- Copy the Client ID (starts with
Iv1.) and Client Secret to.env:
GITHUB_CLIENT_ID=Iv1.your_client_id_here
GITHUB_CLIENT_SECRET=your_client_secret_heresf dev can't run workers, so scripts/dev-functions.mjs runs the app on port 3001 against a local MariaDB, the engine Spacefast uses. scripts/local-db.mjs stands in for env.DB and enforces the platform's limits, so a query that would fail there fails here first.
npm run db:start # Start MariaDB 11.8 in Docker
npm run seed # Create the tables and add test data
npm run dev # Run the app
npm run test:functions # Run the tests
npm run db:stop # Stop MariaDB, which also clears its dataThe application will be available at:
- Frontend: http://localhost:3000
- API: http://localhost:3001
- Add a repository: Pick one from GitHub, or create a custom one with a URL
- Define metrics: In Settings → Metrics, add a key, name and unit for each value you want to track
- Copy the token: Generate a metrics token in the same tab
- Report from CI: Post your results for each commit (see below)
- Share it: Optionally make the dashboard public from Settings → Metrics
CI reports results with a single request:
curl -X POST "https://codevitals.run/api/log?token=YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"branch": "trunk",
"hash": "abc123",
"timestamp": "2026-01-01T00:00:00Z",
"metrics": { "lcp": 2500 },
"baseHash": "def456",
"baseMetrics": { "lcp": 2400 }
}'Only keys that match a metric defined for the repository are stored. Report sizes in bytes: a metric with a unit of KB, MB, GB or TB is converted for display. baseHash and baseMetrics are optional: when the base commit already has a stored value, the new value is normalized against it, so results from different machines stay comparable.
This endpoint is used by CI in several projects outside this repository, so its URL and request format need to stay stable.
/mcp is a Model Context Protocol server, so assistants can answer questions about the projects with public dashboards. It's read-only and needs no sign-in, and the /connect page shows how to add it to an assistant.
| Tool | |
|---|---|
list_projects |
The public projects, and when each last reported |
list_metrics |
What a project measures, with recent levels and how they moved |
get_metric_history |
A metric over a period, one value per commit |
find_changes |
Lasting changes, and the commit each started at |
compare_commits |
Every metric at a commit, against the commits before it or another commit |
find_changes doesn't use the regression flags on the charts, which compare each point with the one before it, so most of them are noise that the next commit undoes. functions/_lib/steps.js looks for lasting changes instead, comparing the 15 commits either side of each commit. It was tuned by shuffling each metric's public history, which removes real changes and keeps the noise, so whatever it finds there is a false positive: about one across all 133 metrics, against 25 changes in the real data.
codevitals/
├── client/ # React frontend (TypeScript + Vite)
│ └── src/
│ ├── components/ # Reusable UI components
│ ├── data/ # API clients and React Query hooks
│ ├── layouts/ # Page layouts
│ ├── pages/ # Application pages
│ └── utils/ # Utility functions
├── functions/ # The API, MCP server and step detection, as Spacefast Functions
├── scripts/ # Local development, seeding and the Spacefast build
├── package.json # Root workspace configuration
└── README.md
Seed the database with sample data for local development:
# Add test data (preserves existing data)
npm run seed
# Reset and re-seed (clears all data first)
npm run seed:resetThe test data includes:
WordPress/gutenberg: a public GitHub repository, with the metrics tokenlocal-dev-gutenberg-tokentestdev/internal-tools: a private custom repository, with the metrics tokenlocal-dev-internal-tools-token- 9 metrics on each, with 600 data points over 60 days and clear regressions and improvements
testdev, the user you sign in as, andtestcollaborator, a user to add as a collaborator
Anything that calls GitHub, such as the GitHub tab of Add Repository, won't work as the test user because its token is fake.
Pushing to main deploys production, so merging a pull request is a deploy. To publish to another space:
npm run build:spacefast
npx spacefast publish dist-spacefast --space <space>A space needs these variables, set with sf env set <name> --secret --value-from-stdin:
| Variable | |
|---|---|
SESSION_SECRET |
Signs sessions. Changing it signs everyone out. |
GITHUB_CLIENT_ID, GITHUB_CLIENT_SECRET |
The GitHub App. Add https://<host>/auth/github/callback to its callback URLs. |
MIGRATION_KEY |
Turns on POST /api/admin/schema, which creates the tables on a new space when sent with the key in an x-migration-key header. Unset it afterwards. |
These aren't in Spacefast's docs, so they're worth knowing when changing queries:
- A statement can have 256 bound parameters at most, and a database operation can be 64 KB at most. Rows go in as one JSON parameter through
JSON_TABLEfor that reason. - About 8 simultaneous requests get 429s, so a page shouldn't send a request per metric.
- Each query is a network call of about 55 ms, so reads that belong together are joined into one query.
- Route files must export named HTTP methods. A bundled
export defaultisn't detected, and neither are exports followed by a comment, which is why the build strips license comments. - All routes run as one worker, so a dependency in one route adds to the startup of all of them. The MCP SDK takes the worker from 157 KB to about 880 KB, which added about 30 ms to warm requests and up to 700 ms to the first one after a deploy.
functions/can't serve paths in dot directories like.well-known._redirectscan, and the build uses it to answer the paths assistants check for a sign-in server, which would otherwise get the app's page and a 200.- On a private space, a machine token's
/*only covers one path level, and a 404 rule also needs the token to cover its destination.
- Built with @wordpress/components
If you encounter any issues or have questions:
- Check existing GitHub Issues
- Create a new issue with details about your problem
- Include relevant logs and configuration (remove sensitive data)