mcp server
fixpoint
Self-improving performance harness for React Native. Measure. Fix. Re-measure. Repeat until nothing changes.
Set up
Runs on your computer. Pick your agent and operating system.
This project does not publish an install method we can turn into a config. Follow the README instead.
README
synced Oct 26, 2026Fixpoint
Measure. Fix. Re-measure. Repeat until nothing changes.
Fixpoint is an open-source, self-improving performance harness for React Native. It connects to a running dev build through React Native DevTools, records the render waterfall and JS flamegraph for every screen, turns them into deterministic findings, applies one fix from an allow-list, re-measures the fix against the original in an interleaved A/B on the same simulator, and opens a draft pull request only when the win survives the gates. No human in the middle until code review.
A fixpoint is the value where applying the function again changes nothing. Fixpoint iterates measure → fix → measure until the app reaches one.
Why
Most AI coding agents stop at "you should memoize this". Fixpoint closes the loop. The model is not the story; the harness is: the wiring from a DevTools trace to a validated, reviewable PR with the proof video attached.
Three ideas carry it:
- Don't measure time. Measure work. React 19.2 reports every component render, every props diff and every scheduler phase through its performance tracks. Render counts have no error bar.
- Run A/A before A/B. The noise floor of base-against-base is the real acceptance threshold, never a hard-coded number.
- Zero edits to the app. Everything attaches from outside: Metro's inspector proxy, the simulator, the filesystem. Navigation goes through Expo Router's own API over the debugger socket.
Status
Version 0.1.0, not yet on npm. What has run for real against a production Expo app is in docs/RESULTS.md: init, verify, the startup capture, the React Compiler pass and a 25-route scan. The A/A, A/B, draft-PR and video steps are implemented and tested against fixtures but have not produced results yet; the results page says so rather than pretending.
Install (from source)
Requirements: macOS with Xcode and a booted iOS Simulator, Node 22+, pnpm, an Expo app using the dev client (SDK 52+) with React Native 0.81+ and React 19.2+, and the app's own dependencies installed.
git clone https://github.com/iamxicor/fixpoint.git
cd fixpoint
pnpm install && pnpm build
alias fixpoint="node $PWD/packages/cli/dist/bin.js" # or: pnpm link --global in packages/cli
60-second quickstart
With Metro running in your app (npx expo start --dev-client) and the app open on the simulator:
cd ~/your-app
fixpoint init # detects scheme, bundle id, routes; writes fixpoint.config.mjs (import-free)
fixpoint verify # connects, records two seconds, checks the React tracks
fixpoint scan # cold-starts once for the startup capture, then records every route
fixpoint findings # ranked table per route; --json for findings.json
init writes one file next to your package.json and nothing else. Everything Fixpoint produces goes to ~/.cache/fixpoint/<app>/.
Walkthrough: measuring a branch, then a fix
# 1. serve a clean ref from its own worktree (keeps your Metro untouched); leave it running
fixpoint serve --ref origin/main --port 8091
# 2. verify and scan against it
fixpoint verify --metro http://127.0.0.1:8091
fixpoint scan --metro http://127.0.0.1:8091
fixpoint findings /notifications
# 3. put one allow-listed fix on a branch (see docs/FIX-RECIPES.md), commit it, then calibrate and measure
fixpoint aa --route /notifications --base origin/main
fixpoint ab --route /notifications --base origin/main --candidate fixpoint/notifications-stable-row-props --video
# 4. gates and the PR text
fixpoint gates --dir <candidate checkout> --verdict ~/.cache/fixpoint/<app>/ab/<run>/verdict.json --out gates.json
fixpoint pr-body --verdict …/verdict.json --findings ~/.cache/fixpoint/<app>/scan/notifications/findings.json \
--finding <id from findings.json> --recipe stable-row-props --gates gates.json --out body.md
gh pr create --draft --base main --title "$(…)" --body-file body.md
Routes that need parameters, auth or a flow (onboarding, pickers, camera) are listed by fixpoint routes with a reason; put them in exclude or give them params in the config.
With the Claude Code plugin (claude --plugin-dir <fixpoint>/plugin, run from your app's directory) the same loop is one command:
/fixpoint:optimize-screen /notifications
…which picks the top finding, applies one allow-listed fix on a branch, runs the gates and an interleaved A/B, and opens a draft PR or reverts.
How it works
flowchart LR
A[Metro inspector proxy] -->|CDP Tracing| B[devtools]
B -->|trace + sampling profile| C[analyzer]
C -->|findings.json| D[agent: one fix from the allow-list]
D -->|branch| E[harness A/B]
E -->|two worktrees, two Metros, one simulator, A B A B| F{verdict}
F -->|accept + gates| G[draft PR with video + traces]
F -->|reject / inconclusive| H[revert, record anyway]
E -.->|A/A first| I[noise floor]
Packages: @fixpoint/devtools (protocol), @fixpoint/analyzer (pure findings), @fixpoint/harness (the method), fixpoint (CLI), @fixpoint/mcp (MCP server for any agent), plugin/ (Claude Code skills). See docs/ARCHITECTURE.md.
Measurement method
The long version is docs/MEASUREMENT.md. The short version:
- Work, not time. Deterministic counts (
componentRenders,avoidableRenders,commits,maxComponentsPerCommit) are primary and compared exactly.renderMsis secondary and reported with a 95 % bootstrap CI and a Wilcoxon p-value. - Visit discipline. Screens are measured on the second visit so lazy module initialisation lands in neither variant. Startup is always a cold launch.
- Interleaved A/B. Two git worktrees, two Metro servers, one simulator, A B A B A B through the dev-client deep link. First pair discarded. Six pairs by default.
- A/A calibration before any A/B; its spread is the noise floor. An A/A "win" fails the harness, not the app.
- Control frames. Self time of functions in files the diff did not touch must stay flat; otherwise the pair is environment drift and is discarded.
- Replay proxy. Record-and-replay in front of the API; replay never forwards; mutations never reach a server.
- Two passes. Full instrumentation to diagnose, light counters to verify.
- Gates. typecheck, lint, existing tests, pixel diff ≤ 0.5 %, verdict
accept. Always a draft PR. Never auto-merge.
What it cannot see
The native main thread (layout, image decode, text measurement, shadow-tree commits), release-build behaviour, frame timing on React Native 0.85 (no frame events in CDP; frame-drop falls back to JS busy runs and says so), Android, bare React Native, physical devices. Every number Fixpoint prints is from a dev build on the iOS Simulator and is labelled so.
Findings
findings.json (schema) has eight kinds: render-fanout, wasted-render, hot-component, long-task, frame-drop, startup-critical-path, heap-growth, compiler-bailout. Each carries a primary metric with a deterministic flag and a method, evidence (symbolicated frames, components, commits), a source location, suggested fix ids and a plain-English summary. The agent never reads a raw trace.
Fixes
The allow-list (docs/FIX-RECIPES.md): remove a React Compiler bailout cause; stable row props and a memo boundary; move state down or split a context; lazy-require a startup module; FlashList/FlatList configuration; hoist inline literals; remove an effect that causes a redundant commit; add missing subscription cleanup. Business logic, data fetching and navigation structure are off limits.
Configuration
fixpoint.config.mjs (written by init, import-free so it never touches your build):
| key | meaning | default |
|---|---|---|
scheme | deep-link scheme (used only for the dev-client cold-start URL) | from app config |
bundleId | iOS bundle identifier of the dev client | from app config |
simulator | simulator name or udid | the booted one |
metroPort / metroUrl | the running Metro scan attaches to | 8081 |
routes[] | { path, params?, scenario? } for dynamic routes and overrides | [] |
exclude[] | route paths or prefixes to skip | [] |
apiBaseEnvVar | EXPO_PUBLIC_* variable the replay proxy overrides | detected |
replay | { mode: 'record' | 'replay' | 'off', dir, port } | off |
thresholds | { minEffect, alpha, maxControlDrift, analyzer } | 0.05, 0.05, 0.25 |
interactions | cdp (scroll/tap through DevTools), idb, mcp, none | cdp |
pairs | measured A/B pairs | 6 |
abPorts | Metro ports for base and candidate | [8091, 8092] |
outDir | traces, screenshots, worktrees, verdicts | ~/.cache/fixpoint/<app> |
CLI
| command | what |
|---|---|
fixpoint init [dir] | detect the app, write the config, print a summary |
fixpoint verify | connect to the dev build, record two seconds, assert the React tracks |
fixpoint routes | list discovered routes with skip reasons |
fixpoint scan [route] | cold-start for the startup capture, record every route, analyse |
fixpoint findings [route] | ranked tables from the latest scan |
fixpoint analyze <trace> | offline: trace (+ cpuprofile, heap snapshots, startup capture, compiler bailouts) → table or JSON |
fixpoint aa --route --base | A/A calibration, records the noise floor |
fixpoint ab --route --base --candidate [--video] | interleaved A/B, verdict JSON |
fixpoint gates --dir --verdict | typecheck, lint, tests, pixel diff, verdict |
fixpoint pr-body … | PR title and body from verdict + findings + gates |
fixpoint baseline write|check | deterministic regression guard for CI |
fixpoint report | RESULTS markdown from verdict files |
fixpoint serve --ref --port | worktree + Metro for a git ref |
Plugin skills
/fixpoint:init, /fixpoint:scan [route], /fixpoint:optimize-screen <route>, /fixpoint:optimize-app, /fixpoint:baseline, /fixpoint:report. The MCP server (fixpoint_verify, fixpoint_scan, fixpoint_findings, fixpoint_ab, fixpoint_aa, fixpoint_gates, fixpoint_pr_body, fixpoint_baseline, fixpoint_report, fixpoint_routes) works with any MCP client.
FAQ
Does it change my app? No. It writes fixpoint.config.mjs next to your package.json and everything else goes to ~/.cache/fixpoint/<app>. Fixes are proposed as draft PRs.
Does it send anything anywhere? No telemetry of any kind. The only network calls are to your Metro, your simulator, and (in record mode) your own API through the local proxy.
Why not just use the Performance panel? You should. Fixpoint reads the same trace the panel shows, but turns it into counts that can be compared exactly between two builds, and automates the A/B.
What about Android / release builds / bare RN? Out of scope for v1. See docs/DECISIONS.md for what was verified and why.
Which numbers in this README are real? None are quoted here; the ones in docs/RESULTS.md all come from files in docs/results/.
Why did the first scan need three harness fixes? Real apps throw during scenarios, have screens without scroll views, and fall into error boundaries. Each case is now detected and recovered from with a cold start; docs/DECISIONS.md has the details.
Contributing
See CONTRIBUTING.md. pnpm install && pnpm build && pnpm test runs everything against fixtures; no simulator needed.
Licence
MIT. No telemetry.