waleedmustafa971/React-surgeon

Click a bug in your React app. A local AI agent traces it to the JSX, fixes it, and only calls it fixed when a real browser replay passes. No cloud, no API keys.

0

stars

0

commits

TypeScript

primary language

Sep 7, 2026

updated

www.npmjs.com/package/@react-surgeon/cli
ai
ai-agent
cli
debugging
developer-tools
javascript
llama-cpp
local-llm
playwright
react
reactjs
testing
typescript
vite
vite-plugin
Browse cluster: React state management and UI libraries

README

React Surgeon

React Surgeon

Click a bug. Watch it get diagnosed, fixed, and verified.

A local, React-specific repair agent. No cloud, no API keys, no telemetry.

CI MIT license Node 22.12+ Local CPU inference


React Surgeon closes the loop between seeing a bug in your running React app and proving it is gone.

A development-only Vite plugin maps clicked DOM elements back to the JSX that rendered them. Deterministic Babel analysis narrows the surrounding code to a small, relevant context for a local Qwen Coder model. A bounded agent proposes exact, minimal edits. Static checks and a Playwright acceptance scenario — not the model's own opinion — decide whether the change is VERIFIED.

Everything runs on your machine, on CPU.

Clicking Add to Cart adds two items instead of one; selecting the button reports src/components/ProductCard.tsx:19

One click adds two. Selecting the button reports the exact JSX that rendered it — src/components/ProductCard.tsx:19 — without firing the app's own handler.

Contents

How it works

flowchart LR
  DOM[Instrumented React DOM] --> Overlay[Selection overlay]
  Overlay -->|authenticated localhost WebSocket| Bridge[Source selection bridge]
  Bridge --> UI[CLI / VS Code]
  UI --> Agent[Bounded repair agent]
  AST[Babel component and import graph] --> Agent
  Agent --> Model[llama.cpp CPU model]
  Agent --> Patch[Exact-match patch and undo]
  Patch --> Static[Typecheck / lint / test / build]
  Static --> Browser[Playwright replay and assertions]
  Browser --> Proof[Persisted JSON proof]
  Browser -->|compact failure| Agent
  1. Locate. The Vite plugin stamps source coordinates onto intrinsic JSX elements during serve only. The overlay highlights what you hover and reports the file, line, role and accessible name of what you click.
  2. Understand. Babel builds a component, hook, route and import graph. The agent assembles context from the clicked element, its imports, related components, and words in your task description — not the whole repository.
  3. Reproduce. The agent runs your acceptance scenario against the unmodified app first. If it already passes, the task is refused rather than inventing a change.
  4. Repair. The local model returns one structured action at a time: a read-only inspection tool, or an exact-match replacement of a unique existing substring.
  5. Verify. Inference stops, freeing memory. Typecheck, lint, tests and a production build run, then the same scenario is replayed in a fresh Chromium context.
  6. Prove. A JSON proof records the original failure, the exact diff, every check result, and every assertion. VERIFIED is only returned when all of it passes.

Requirements

  • Node.js 22.12+ and npm.
  • A React project — Vite-based for element selection.
  • Linux, macOS or Windows on x64/arm64. CI covers all three.

Nothing else. The llama.cpp runtime arrives as a prebuilt binary through npm, so there is no system install, no CUDA, no Docker, no Python, no API keys, no cloud service, no telemetry, and no subscription. Inference is CPU-only and stays on your machine. Designed to fit 6 GB RAM by unloading the model before static checks and browser launch; running other heavy applications may still cause paging.

Install

npm install -g @react-surgeon/cli
cd your-react-app
npm install -D @react-surgeon/vite-plugin
react-surgeon init

init does the whole setup in one command: detects your React project, writes .react-surgeon/config.json, adds surgeon() to your Vite config ahead of the React plugin, installs Chromium, then downloads and smoke-tests the model. It is idempotent, so re-run it after fixing anything it flags. Use --skip-browser or --skip-model to defer the slow parts.

The Vite plugin is what maps a clicked element back to its JSX, so it belongs to the app being repaired rather than the CLI.

Minimal install, if you already run Ollama or LM Studio

The bundled runtime is an optionalDependency, so it can be skipped:

npm install -g @react-surgeon/cli --omit=optional

That takes the dependency tree from 120 packages to 24, with no install scripts at all, leaving only Babel, playwright-core, commander, diff, ws and zod. Everything node-llama-cpp brings with it, including its native binaries and its postinstall, is gone.

Then set model.provider to openai-compatible and point it at a server you already run. doctor will report bundledRuntime: Missing, which is expected on this path.

Worth knowing if you audit dependencies: the default install needs filesystem access to edit your source, shell access to run your npm scripts, and network access to download the model and Chromium. Those are inherent to what the tool does rather than incidental.

The first model download is roughly 1 GB and takes a few minutes. `react-surgeon doctor` reports OS, RAM, CPU, Node, which provider is configured and where inference will happen.
Working from a clone of this repository instead
npm install
npm run build
npm run surgeon -- init

Then use npm run surgeon -- COMMAND wherever the docs say react-surgeon COMMAND.

Quick start

Three terminals. First, start the demo app:

npm run demo

Second, start the selection bridge:

npm run surgeon -- --project examples/buggy-react-app start

Open the URL it prints — it carries a one-time session capability that authenticates the localhost bridge. Click Select UI, then hover and click Add to Cart. Selection suppresses the app's own click handler and reports the source file and line. The model stays asleep until it is needed.

Third, repair and prove:

npm run surgeon -- --project examples/buggy-react-app fix "Each Add to Cart click should add exactly one item" --scenario scenarios/cart.json
npm run surgeon -- --project examples/buggy-react-app proof

A successful run looks like this:

Reproducing the original behavior…
Tracing src/components/ProductCard.tsx:19 from browser reproduction
Diagnosing (step 1/12, patch cycle 1/4)…
Patching src/components/ProductCard.tsx…
Checking typecheck… lint… test… build…
Replaying browser scenario…
VERIFIED — static checks and browser assertions passed
   function handleAdd() {
-    setCount(count + 2);
+    setCount(count + 1);
   }

The demo bugs

examples/buggy-react-app ships three reproducible defects, each with a matching scenario.

BugSelectExpected behaviourScenario
Incorrect updateAdd to CartOne item per click; the initial code adds twoscenarios/cart.json
Login redirectSign inNavigates to /dashboard; the initial code navigates to /loginscenarios/login.json
Mutated stateRemove KeyboardThe list immediately shows two items; the initial code mutates the same arrayscenarios/list.json

Restore all three to their documented starting state at any time:

npm run reset:demo

This overwrites only those three component files from stored fixtures — do not run it if you have manual edits there. Refresh the browser afterwards to clear local state. npm run check:demo verifies the demo is in its documented buggy state and is enforced by a pre-commit hook, so agent edits are never committed into the lab.

CLI reference

Run react-surgeon [--project PATH] COMMAND, or npm run surgeon -- COMMAND from a clone of this repository.

CommandPurpose
initSet up this project: config, Vite plugin, Chromium, model
doctorOS, RAM, CPU, Node, provider, and where inference will happen
model setupDownload/cache, start, inference test, stop
model startKeep the owned model running in the foreground
model stopStop the owned foreground model through its authenticated local control endpoint
scanReact libraries, scripts and entry points
xrayComponents, hooks and relationships as a text graph
healthDeterministic diagnostic hints (for example, direct state mutation)
startModel smoke test plus the selection and recording bridge
scenario newDraft an acceptance scenario from the last recorded reproduction
diagnose "task"Read-only local model diagnosis; never patches
fix "task" --scenario file.jsonReproduce, patch, check, replay, save proof
verify --scenario file.jsonVerify the current project state without patching
proofPrint the last persisted proof
undoRestore the original files from the last patch session

Global options: --project <path> selects the React project root (defaults to the current directory), --verbose enables detailed lifecycle logs.

init takes --skip-browser and --skip-model. scenario new takes --out <file> (default scenarios/draft.json) and --name <name>.

fix and verify exit with code 1 when the result is not VERIFIED, so they compose in scripts.

Acceptance scenarios

A scenario is a JSON file with name, baseURL, and 1–200 steps. It is the acceptance contract: you write it, and it decides the outcome.

{
  "name": "One click adds exactly one item",
  "baseURL": "http://127.0.0.1:5173",
  "steps": [
    { "action": "navigate", "url": "/" },
    { "action": "assertText", "testId": "cart-count", "value": "0" },
    { "action": "click", "role": "button", "name": "Add to Cart" },
    { "action": "assertText", "testId": "cart-count", "value": "1" },
    { "action": "click", "role": "button", "name": "Add to Cart" },
    { "action": "assertText", "testId": "cart-count", "value": "2" }
  ]
}
ActionFieldsNotes
navigateurlRelative to baseURL; localhost only
reloadReloads the current page
clickrole + name, testId, or selector
filltarget + valuevalue is capped at 2000 characters
submittargetSubmits the owning form
assertTexttarget + valueExact match, not a substring
assertVisibletarget
assertURLvalue

Prefer accessible roles and names, or data-testid, over CSS selectors — they survive refactors. Browser navigation and network requests are restricted to localhost.

Recording a scenario. While start is running, the overlay recorder captures clicks, changed inputs, form submissions and back/forward navigation into .react-surgeon/recording.json. Password fields are recorded as [REDACTED] and cannot be replayed without a locally supplied demo value.

Drafting one from the recording. Writing the scenario is the part that actually costs time — finding stable targets and deciding what to assert. scenario new does the mechanical half:

react-surgeon scenario new --out scenarios/cart.json --name "One click adds exactly one item"

It replays your recording in a real browser, watches which data-testid values and which URL change, and writes a scenario that asserts the starting values, performs your recorded interactions, and asserts the ending values:

Observed during the reproduction:
  cart-count: 0 -> 4

The catch, and it is deliberate: those ending values are what the app currently does, which is the bug. The draft is labelled accordingly and carries a _draft block listing exactly what to change. Edit 4 to the 2 you actually expect, delete the block, and run fix. The tool finds the targets; you still decide what correct means — which is the same reason it refuses to invent acceptance criteria anywhere else.

If nothing changed during the reproduction, it says so: add data-testid to the values that should change and re-record.

Verification, proof, and undo

The agent runs your scenario against the original app first. If it already passes, the task is blocked rather than fabricating a reproduction.

After editing, it runs the available typecheck, lint, test and build scripts — missing optional scripts are skipped — and then replays the scenario.

VERIFIED requires all of:

  • a successful production build,
  • no failed static checks,
  • a passing browser replay with at least one assertion,
  • no console errors, page errors, or failed requests.

Anything else is BLOCKED. This distinction is the whole point of the tool: a patch that typechecks, lints, tests and builds cleanly can still be wrong, and only the browser replay catches it.

Unverified edits stay on disk. When a run ends BLOCKED, the last attempted patch is still in your working tree so you can inspect it. The CLI says so explicitly and prints the undo command. Nothing is committed, pushed or deployed on your behalf.

npm run surgeon -- --project examples/buggy-react-app undo

undo restores the originals from the last patch session, preserving original line endings.

Proof. Every run writes .react-surgeon/proofs/<timestamp>.json and updates latest.json, containing the original failure, the task, the acceptance assertions, the exact patch diff, all check output, and the replay result. A proof is evidence about a particular workspace state — later manual edits or an undo invalidate its applicability to the current files. Treat .react-surgeon/ as private workspace data; it can contain source diffs and scenario input.

The repair agent

  1. Detect React from package.json and scan the source with Babel.
  2. Run the acceptance scenario on the original app to capture a real failure.
  3. Assemble source context from the UI location, imports, related components and task words.
  4. Ask the local model for one action: a semantic read/search tool, a minimal exact replacement, or completion.
  5. Validate the JSON strictly; allow one malformed-format retry.
  6. Apply only a unique exact match in a .js/.jsx/.ts/.tsx/.css file, persisting the original first.
  7. Stop inference, run the static checks, then replay the same scenario.
  8. Persist proof. Return VERIFIED only if everything passes.
  9. Otherwise narrow the context around a compact failure summary and try again.

Budgets. Up to 12 model steps total (read-only tool calls included) and at most 4 patch-and-verify cycles; each cycle runs the full static suite and a browser replay. Progress is reported as Diagnosing (step N/12, patch cycle M/4). Repeating an identical action is blocked to prevent loops.

Failed edits never fall back to rewriting whole files. The model cannot execute arbitrary commands, choose a script outside the verifier's allowlist, create or delete files, or modify package.json. Its completion text is never treated as proof.

The 4096-token window reserves a modest generation allowance and uses a conservative character budget rather than a perfect tokenizer, so unusually token-dense source can still exhaust the context. No conversation history and no full repository snapshot are ever sent.

Configuration

.react-surgeon/config.json is created with defaults on first run.

KeyDefaultPurpose
model.providernode-llama-cppnode-llama-cpp (bundled, nothing to install), llama.cpp (a llama-server on PATH), or openai-compatible (a server you already run)
model.repositoryQwen/Qwen2.5-Coder-1.5B-Instruct-GGUFHugging Face repository
model.quantizationQ4_K_MOr Q3_K_M for tighter memory
model.contextSize40962048–16384
model.threads4CPU threads for inference
model.modelPath(auto)Absolute path to a GGUF, to share one cache across projects
model.executable(auto)llama.cpp only: path to llama-server if not on PATH
model.port18081llama.cpp only: local inference port
model.baseURLhttp://127.0.0.1:11434/v1openai-compatible only: where the server listens
model.modelqwen2.5-coder:1.5bopenai-compatible only: model name the server exposes
model.apiKeyEnv(none)openai-compatible only: name of the environment variable holding a key, never the key itself
model.allowRemotefalseopenai-compatible only: permit a non-loopback endpoint
memoryModelowlow unloads the model before verification
verification.lint / .test / .build / .browsertrueDisable a stage if your project lacks it
bridgePort18080Selection bridge port
appURLhttp://127.0.0.1:5173Where your dev server runs

The model

Three providers, all behind one ModelProvider interface. Default: Qwen/Qwen2.5-Coder-1.5B-Instruct-GGUF:Q4_K_M, 4096-token context, temperature 0.1, a single generation, four CPU threads, zero GPU layers.

node-llama-cpp (default). llama.cpp compiled into a prebuilt binary that npm installs for your platform — nothing to put on PATH. It runs in-process, and the agent disposes the context and weights before static checks and the browser replay, which is what keeps the pipeline inside 6 GB. If your platform has no prebuilt binary, install fails softly and doctor reports bundledRuntime: Missing.

llama.cpp. Drives a llama-server (or llama serve) found on PATH, as a separate process on model.port. Use it when you want your own build — a different quantization, GPU offload, or a patched llama.cpp. Install it with winget install ggml.llamacpp or from the llama.cpp releases.

openai-compatible. Points at a server you already run — Ollama, LM Studio, llama-server, vLLM. React Surgeon does not own that process, so it never starts or stops it; memoryMode: low cannot unload someone else's server before the browser phase, so leave headroom yourself.

{
  "model": {
    "provider": "openai-compatible",
    "baseURL": "http://127.0.0.1:11434/v1",
    "model": "qwen2.5-coder:1.5b"
  }
}

The endpoint must be loopback. A non-local baseURL is refused unless you explicitly set model.allowRemote, because sending your source code off the machine should be a decision, not a default. An API key is read from the environment variable named by apiKeyEnv and never stored in the config file.

Generation is constrained to JSON: the two llama.cpp providers enforce a grammar, and because an arbitrary OpenAI-compatible server may ignore response_format entirely, responses are also unwrapped from markdown fences and surrounding prose before parsing. Repository text is treated as untrusted data throughout.

VS Code extension

npm run package:vscode
code --install-extension dist/react-surgeon-0.2.0.vsix
code examples/buggy-react-app

Alternatively use Extensions → … → Install from VSIX. Start the demo in a terminal, open the React Surgeon Activity Bar, choose Connect project, then Select UI element. Enter a bug description, choose Diagnose & Fix, and select an acceptance scenario when prompted. The panel also exposes verification, proof and X-Ray.

Workspace trust is required to execute project scripts. The extension needs Node and npm on the host.

The extension cannot use the bundled node-llama-cpp runtime. That package is a platform-specific native binary, so embedding it would make each VSIX installable on exactly one operating system. Inside VS Code, set model.provider to llama.cpp (a llama-server on PATH) or openai-compatible (an Ollama or LM Studio you already run). The CLI has no such limitation.

Use with your own React project

Install or link @react-surgeon/vite-plugin from this workspace, then add surgeon() before the React plugin:

import surgeon from "@react-surgeon/vite-plugin";
import react from "@vitejs/plugin-react";

export default defineConfig({
  plugins: [surgeon(), react()],
});

The plugin uses apply: 'serve', instruments intrinsic JSX elements only, and leaves production output untouched.

Then configure .react-surgeon/config.json for your app URL and, if 5173/18080/18081 are occupied, alternative ports. Provide a Vite dev script for automatic dev-server launch; otherwise start the app yourself. An existing user-owned server on the configured URL is always reused, never replaced.

Architecture

PackageRole
@react-surgeon/sharedShared types and schemas
@react-surgeon/coreAnalysis, agent, model provider, patching, verification, bridge
@react-surgeon/vite-pluginDev-only JSX source instrumentation and selection overlay
@react-surgeon/clireact-surgeon command line
packages/vscodeVS Code extension
examples/buggy-react-appThe demo lab — an ordinary React/Vite project

There is exactly one agent and one inference process, with browser verification serialized after inference shutdown.

The plugin runs before JSX compilation, and MagicString inserts source attributes into intrinsic JSX opening tags while preserving source maps. A shadow-root overlay highlights hovered nodes and intercepts the selection click. Metadata uses project-relative paths only. The bridge binds 127.0.0.1, validates the origin and a random session capability, and validates every received source path.

Babel extracts components, props, hooks, state, events, routes and ordinary relative imports; names and resolved imports determine likely parent relationships. This is deliberately conservative — higher-order wrappers and dynamic imports need better analysis in a later version.

ResourceManager shuts the model down before launching verification. Vite is started only if the configured URL is unavailable. A single Chromium instance is created per replay and closed in a finally. Process execution captures bounded output with timeouts, and Ctrl+C shuts down owned services.

See docs/ARCHITECTURE.md and docs/AGENT.md.

Security boundaries

Use React Surgeon only with trusted local projects. It is an MVP, not an operating-system sandbox. Full detail is in docs/SECURITY.md.

  • Filesystem. Model-facing paths are canonicalized inside the selected workspace. Absolute paths, traversal, escaping symlinks and junctions, .env variants, SSH material, common credential paths, node_modules and build output are blocked. Search skips symlinks and hidden directories. Patching is restricted to React source and CSS.
  • Execution. Only predetermined npm script categories run: typecheck, lint, test, build, and the Vite dev server. A package script can itself run arbitrary code, so an allowlisted name does not make an untrusted repository safe. VS Code workspace trust is required.
  • Network. The bridge and the model server both bind 127.0.0.1. Neither opens a firewall port. Playwright navigations and requests are restricted to localhost in a fresh non-persistent context; personal browser profiles and password stores are never opened.
  • Secrets. Session URLs contain a local capability — do not share them. The overlay strips it from the URL and keeps it in tab session storage. Context and command summaries redact common secret assignments and token patterns, but this is heuristic: do not put production credentials in source or scenarios.
  • Not exposed to the model. Commits, pushes, deployments, cloud calls, arbitrary shell tools, dependency installation and file deletion.

Core inference stays local. npm packages, browser binaries and GGUF weights are downloaded from their distribution servers; once cached, coding and verification need no hosted AI service. There is no telemetry.

Review every diff before committing or deploying.

Development

npm run typecheck
npm run lint
npm test
npm run build
npm run check:demo
npm run test:vsix
npm run test:integration
npm run package:vscode
ScriptWhat it does
buildEmits each workspace's dist entry with esbuild
typecheck / lint / testThe normal checks; unit tests use isolated temp folders and one Vitest worker to limit memory
check:demoFails if the demo app is not in its documented buggy state
reset:demoRestores the three demo defects from fixtures
test:vsixExtracts the VSIX and verifies isolated activation, commands, CSP and the packaged Chromium runtime
test:integrationFull end-to-end run against the real Qwen model
package:vscodeBundles the extension with esbuild and packages it with @vscode/vsce

npm install also installs the repository git hooks (core.hooksPath.githooks). The pre-commit hook runs check:demo so an agent's working-tree edits can never be committed into the demo lab — that would break npm run lint on a fresh clone and turn the lab into a no-op.

The integration test is not a mock: it exercises hover, selection, source mapping, a model-generated patch, static checks, browser assertions and proof, then restores the demo defect to keep the lab repeatable. Evidence is copied to dist/integration-proof.json.

The demo app has its own typecheck, lint, test and build scripts, so the agent verifies the target application rather than its own code.

Windows execution policies or managed sandboxes may block child processes, downloads or cache writes. Use the environment's explicit approval mechanism; do not bypass it.

External references: Vite plugin API · llama.cpp server · Playwright · VS Code extension API.

Limitations and troubleshooting

Limitations

  • This is an MVP, not production-grade autonomous repair. A 1.5B model can misdiagnose or exhaust the retry budget — in practice it solves the demo defects on the first cycle sometimes and fails entirely on others. That is exactly why verification, not the model, decides. Review every diff.
  • Analysis resolves ordinary relative imports. Aliases, higher-order wrappers, re-exports, dynamic routes and complex provider relationships may need manual context.
  • X-Ray is a text graph, and health output is a set of hints, not proofs.
  • npm is the supported command runner; other lockfile package managers are detected but not driven. Vitest and Jest are given unattended flags when needed.
  • Automatic acceptance generation is not implemented — supply explicit scenarios. The recorder does not yet capture every SPA history transition or full-page navigation across reloads.
  • model stop controls only an owned model start process in the same workspace; it never terminates arbitrary llama.cpp processes. Ctrl+C also shuts down owned processes.

Troubleshooting

SymptomFix
Blank overlay connectionNo active bridge, or the app was opened without the printed session URL. Restart start and use the new URL.
Missing Chromiumnpx playwright install chromium
Port conflictStop the owning process, or set another port in .react-surgeon/config.json
Model fails to startnpm run surgeon -- --verbose model setup; check PATH and free RAM. executable and modelPath accept absolute paths.
BLOCKED: Acceptance scenario already passesWorking as intended — your scenario does not reproduce the bug. Tighten the assertions.
Unverified edits left behindnpm run surgeon -- --project <path> undo

Future work: better alias and provider analysis, wider recorder coverage, automatic test proposals, a richer component tree, React Compiler diagnostics, and optional larger-model profiles. Verification reliability remains the priority.

License

MIT — see LICENSE.

Further reading: DEMO.md · architecture · agent design · security · development.

waleedmustafa971/React-surgeon

Click a bug in your React app. A local AI agent traces it to the JSX, fixes it, and only calls it fixed when a real browser replay passes. No cloud, no API keys.

0

stars

0

commits

TypeScript

primary language

Sep 7, 2026

updated

www.npmjs.com/package/@react-surgeon/cli
ai
ai-agent
cli
debugging
developer-tools
javascript
llama-cpp
local-llm
playwright
react
reactjs
testing
typescript
vite
vite-plugin
Browse cluster: React state management and UI libraries

README

React Surgeon

React Surgeon

Click a bug. Watch it get diagnosed, fixed, and verified.

A local, React-specific repair agent. No cloud, no API keys, no telemetry.

CI MIT license Node 22.12+ Local CPU inference


React Surgeon closes the loop between seeing a bug in your running React app and proving it is gone.

A development-only Vite plugin maps clicked DOM elements back to the JSX that rendered them. Deterministic Babel analysis narrows the surrounding code to a small, relevant context for a local Qwen Coder model. A bounded agent proposes exact, minimal edits. Static checks and a Playwright acceptance scenario — not the model's own opinion — decide whether the change is VERIFIED.

Everything runs on your machine, on CPU.

Clicking Add to Cart adds two items instead of one; selecting the button reports src/components/ProductCard.tsx:19

One click adds two. Selecting the button reports the exact JSX that rendered it — src/components/ProductCard.tsx:19 — without firing the app's own handler.

Contents

How it works

flowchart LR
  DOM[Instrumented React DOM] --> Overlay[Selection overlay]
  Overlay -->|authenticated localhost WebSocket| Bridge[Source selection bridge]
  Bridge --> UI[CLI / VS Code]
  UI --> Agent[Bounded repair agent]
  AST[Babel component and import graph] --> Agent
  Agent --> Model[llama.cpp CPU model]
  Agent --> Patch[Exact-match patch and undo]
  Patch --> Static[Typecheck / lint / test / build]
  Static --> Browser[Playwright replay and assertions]
  Browser --> Proof[Persisted JSON proof]
  Browser -->|compact failure| Agent
  1. Locate. The Vite plugin stamps source coordinates onto intrinsic JSX elements during serve only. The overlay highlights what you hover and reports the file, line, role and accessible name of what you click.
  2. Understand. Babel builds a component, hook, route and import graph. The agent assembles context from the clicked element, its imports, related components, and words in your task description — not the whole repository.
  3. Reproduce. The agent runs your acceptance scenario against the unmodified app first. If it already passes, the task is refused rather than inventing a change.
  4. Repair. The local model returns one structured action at a time: a read-only inspection tool, or an exact-match replacement of a unique existing substring.
  5. Verify. Inference stops, freeing memory. Typecheck, lint, tests and a production build run, then the same scenario is replayed in a fresh Chromium context.
  6. Prove. A JSON proof records the original failure, the exact diff, every check result, and every assertion. VERIFIED is only returned when all of it passes.

Requirements

  • Node.js 22.12+ and npm.
  • A React project — Vite-based for element selection.
  • Linux, macOS or Windows on x64/arm64. CI covers all three.

Nothing else. The llama.cpp runtime arrives as a prebuilt binary through npm, so there is no system install, no CUDA, no Docker, no Python, no API keys, no cloud service, no telemetry, and no subscription. Inference is CPU-only and stays on your machine. Designed to fit 6 GB RAM by unloading the model before static checks and browser launch; running other heavy applications may still cause paging.

Install

npm install -g @react-surgeon/cli
cd your-react-app
npm install -D @react-surgeon/vite-plugin
react-surgeon init

init does the whole setup in one command: detects your React project, writes .react-surgeon/config.json, adds surgeon() to your Vite config ahead of the React plugin, installs Chromium, then downloads and smoke-tests the model. It is idempotent, so re-run it after fixing anything it flags. Use --skip-browser or --skip-model to defer the slow parts.

The Vite plugin is what maps a clicked element back to its JSX, so it belongs to the app being repaired rather than the CLI.

Minimal install, if you already run Ollama or LM Studio

The bundled runtime is an optionalDependency, so it can be skipped:

npm install -g @react-surgeon/cli --omit=optional

That takes the dependency tree from 120 packages to 24, with no install scripts at all, leaving only Babel, playwright-core, commander, diff, ws and zod. Everything node-llama-cpp brings with it, including its native binaries and its postinstall, is gone.

Then set model.provider to openai-compatible and point it at a server you already run. doctor will report bundledRuntime: Missing, which is expected on this path.

Worth knowing if you audit dependencies: the default install needs filesystem access to edit your source, shell access to run your npm scripts, and network access to download the model and Chromium. Those are inherent to what the tool does rather than incidental.

The first model download is roughly 1 GB and takes a few minutes. `react-surgeon doctor` reports OS, RAM, CPU, Node, which provider is configured and where inference will happen.
Working from a clone of this repository instead
npm install
npm run build
npm run surgeon -- init

Then use npm run surgeon -- COMMAND wherever the docs say react-surgeon COMMAND.

Quick start

Three terminals. First, start the demo app:

npm run demo

Second, start the selection bridge:

npm run surgeon -- --project examples/buggy-react-app start

Open the URL it prints — it carries a one-time session capability that authenticates the localhost bridge. Click Select UI, then hover and click Add to Cart. Selection suppresses the app's own click handler and reports the source file and line. The model stays asleep until it is needed.

Third, repair and prove:

npm run surgeon -- --project examples/buggy-react-app fix "Each Add to Cart click should add exactly one item" --scenario scenarios/cart.json
npm run surgeon -- --project examples/buggy-react-app proof

A successful run looks like this:

Reproducing the original behavior…
Tracing src/components/ProductCard.tsx:19 from browser reproduction
Diagnosing (step 1/12, patch cycle 1/4)…
Patching src/components/ProductCard.tsx…
Checking typecheck… lint… test… build…
Replaying browser scenario…
VERIFIED — static checks and browser assertions passed
   function handleAdd() {
-    setCount(count + 2);
+    setCount(count + 1);
   }

The demo bugs

examples/buggy-react-app ships three reproducible defects, each with a matching scenario.

BugSelectExpected behaviourScenario
Incorrect updateAdd to CartOne item per click; the initial code adds twoscenarios/cart.json
Login redirectSign inNavigates to /dashboard; the initial code navigates to /loginscenarios/login.json
Mutated stateRemove KeyboardThe list immediately shows two items; the initial code mutates the same arrayscenarios/list.json

Restore all three to their documented starting state at any time:

npm run reset:demo

This overwrites only those three component files from stored fixtures — do not run it if you have manual edits there. Refresh the browser afterwards to clear local state. npm run check:demo verifies the demo is in its documented buggy state and is enforced by a pre-commit hook, so agent edits are never committed into the lab.

CLI reference

Run react-surgeon [--project PATH] COMMAND, or npm run surgeon -- COMMAND from a clone of this repository.

CommandPurpose
initSet up this project: config, Vite plugin, Chromium, model
doctorOS, RAM, CPU, Node, provider, and where inference will happen
model setupDownload/cache, start, inference test, stop
model startKeep the owned model running in the foreground
model stopStop the owned foreground model through its authenticated local control endpoint
scanReact libraries, scripts and entry points
xrayComponents, hooks and relationships as a text graph
healthDeterministic diagnostic hints (for example, direct state mutation)
startModel smoke test plus the selection and recording bridge
scenario newDraft an acceptance scenario from the last recorded reproduction
diagnose "task"Read-only local model diagnosis; never patches
fix "task" --scenario file.jsonReproduce, patch, check, replay, save proof
verify --scenario file.jsonVerify the current project state without patching
proofPrint the last persisted proof
undoRestore the original files from the last patch session

Global options: --project <path> selects the React project root (defaults to the current directory), --verbose enables detailed lifecycle logs.

init takes --skip-browser and --skip-model. scenario new takes --out <file> (default scenarios/draft.json) and --name <name>.

fix and verify exit with code 1 when the result is not VERIFIED, so they compose in scripts.

Acceptance scenarios

A scenario is a JSON file with name, baseURL, and 1–200 steps. It is the acceptance contract: you write it, and it decides the outcome.

{
  "name": "One click adds exactly one item",
  "baseURL": "http://127.0.0.1:5173",
  "steps": [
    { "action": "navigate", "url": "/" },
    { "action": "assertText", "testId": "cart-count", "value": "0" },
    { "action": "click", "role": "button", "name": "Add to Cart" },
    { "action": "assertText", "testId": "cart-count", "value": "1" },
    { "action": "click", "role": "button", "name": "Add to Cart" },
    { "action": "assertText", "testId": "cart-count", "value": "2" }
  ]
}
ActionFieldsNotes
navigateurlRelative to baseURL; localhost only
reloadReloads the current page
clickrole + name, testId, or selector
filltarget + valuevalue is capped at 2000 characters
submittargetSubmits the owning form
assertTexttarget + valueExact match, not a substring
assertVisibletarget
assertURLvalue

Prefer accessible roles and names, or data-testid, over CSS selectors — they survive refactors. Browser navigation and network requests are restricted to localhost.

Recording a scenario. While start is running, the overlay recorder captures clicks, changed inputs, form submissions and back/forward navigation into .react-surgeon/recording.json. Password fields are recorded as [REDACTED] and cannot be replayed without a locally supplied demo value.

Drafting one from the recording. Writing the scenario is the part that actually costs time — finding stable targets and deciding what to assert. scenario new does the mechanical half:

react-surgeon scenario new --out scenarios/cart.json --name "One click adds exactly one item"

It replays your recording in a real browser, watches which data-testid values and which URL change, and writes a scenario that asserts the starting values, performs your recorded interactions, and asserts the ending values:

Observed during the reproduction:
  cart-count: 0 -> 4

The catch, and it is deliberate: those ending values are what the app currently does, which is the bug. The draft is labelled accordingly and carries a _draft block listing exactly what to change. Edit 4 to the 2 you actually expect, delete the block, and run fix. The tool finds the targets; you still decide what correct means — which is the same reason it refuses to invent acceptance criteria anywhere else.

If nothing changed during the reproduction, it says so: add data-testid to the values that should change and re-record.

Verification, proof, and undo

The agent runs your scenario against the original app first. If it already passes, the task is blocked rather than fabricating a reproduction.

After editing, it runs the available typecheck, lint, test and build scripts — missing optional scripts are skipped — and then replays the scenario.

VERIFIED requires all of:

  • a successful production build,
  • no failed static checks,
  • a passing browser replay with at least one assertion,
  • no console errors, page errors, or failed requests.

Anything else is BLOCKED. This distinction is the whole point of the tool: a patch that typechecks, lints, tests and builds cleanly can still be wrong, and only the browser replay catches it.

Unverified edits stay on disk. When a run ends BLOCKED, the last attempted patch is still in your working tree so you can inspect it. The CLI says so explicitly and prints the undo command. Nothing is committed, pushed or deployed on your behalf.

npm run surgeon -- --project examples/buggy-react-app undo

undo restores the originals from the last patch session, preserving original line endings.

Proof. Every run writes .react-surgeon/proofs/<timestamp>.json and updates latest.json, containing the original failure, the task, the acceptance assertions, the exact patch diff, all check output, and the replay result. A proof is evidence about a particular workspace state — later manual edits or an undo invalidate its applicability to the current files. Treat .react-surgeon/ as private workspace data; it can contain source diffs and scenario input.

The repair agent

  1. Detect React from package.json and scan the source with Babel.
  2. Run the acceptance scenario on the original app to capture a real failure.
  3. Assemble source context from the UI location, imports, related components and task words.
  4. Ask the local model for one action: a semantic read/search tool, a minimal exact replacement, or completion.
  5. Validate the JSON strictly; allow one malformed-format retry.
  6. Apply only a unique exact match in a .js/.jsx/.ts/.tsx/.css file, persisting the original first.
  7. Stop inference, run the static checks, then replay the same scenario.
  8. Persist proof. Return VERIFIED only if everything passes.
  9. Otherwise narrow the context around a compact failure summary and try again.

Budgets. Up to 12 model steps total (read-only tool calls included) and at most 4 patch-and-verify cycles; each cycle runs the full static suite and a browser replay. Progress is reported as Diagnosing (step N/12, patch cycle M/4). Repeating an identical action is blocked to prevent loops.

Failed edits never fall back to rewriting whole files. The model cannot execute arbitrary commands, choose a script outside the verifier's allowlist, create or delete files, or modify package.json. Its completion text is never treated as proof.

The 4096-token window reserves a modest generation allowance and uses a conservative character budget rather than a perfect tokenizer, so unusually token-dense source can still exhaust the context. No conversation history and no full repository snapshot are ever sent.

Configuration

.react-surgeon/config.json is created with defaults on first run.

KeyDefaultPurpose
model.providernode-llama-cppnode-llama-cpp (bundled, nothing to install), llama.cpp (a llama-server on PATH), or openai-compatible (a server you already run)
model.repositoryQwen/Qwen2.5-Coder-1.5B-Instruct-GGUFHugging Face repository
model.quantizationQ4_K_MOr Q3_K_M for tighter memory
model.contextSize40962048–16384
model.threads4CPU threads for inference
model.modelPath(auto)Absolute path to a GGUF, to share one cache across projects
model.executable(auto)llama.cpp only: path to llama-server if not on PATH
model.port18081llama.cpp only: local inference port
model.baseURLhttp://127.0.0.1:11434/v1openai-compatible only: where the server listens
model.modelqwen2.5-coder:1.5bopenai-compatible only: model name the server exposes
model.apiKeyEnv(none)openai-compatible only: name of the environment variable holding a key, never the key itself
model.allowRemotefalseopenai-compatible only: permit a non-loopback endpoint
memoryModelowlow unloads the model before verification
verification.lint / .test / .build / .browsertrueDisable a stage if your project lacks it
bridgePort18080Selection bridge port
appURLhttp://127.0.0.1:5173Where your dev server runs

The model

Three providers, all behind one ModelProvider interface. Default: Qwen/Qwen2.5-Coder-1.5B-Instruct-GGUF:Q4_K_M, 4096-token context, temperature 0.1, a single generation, four CPU threads, zero GPU layers.

node-llama-cpp (default). llama.cpp compiled into a prebuilt binary that npm installs for your platform — nothing to put on PATH. It runs in-process, and the agent disposes the context and weights before static checks and the browser replay, which is what keeps the pipeline inside 6 GB. If your platform has no prebuilt binary, install fails softly and doctor reports bundledRuntime: Missing.

llama.cpp. Drives a llama-server (or llama serve) found on PATH, as a separate process on model.port. Use it when you want your own build — a different quantization, GPU offload, or a patched llama.cpp. Install it with winget install ggml.llamacpp or from the llama.cpp releases.

openai-compatible. Points at a server you already run — Ollama, LM Studio, llama-server, vLLM. React Surgeon does not own that process, so it never starts or stops it; memoryMode: low cannot unload someone else's server before the browser phase, so leave headroom yourself.

{
  "model": {
    "provider": "openai-compatible",
    "baseURL": "http://127.0.0.1:11434/v1",
    "model": "qwen2.5-coder:1.5b"
  }
}

The endpoint must be loopback. A non-local baseURL is refused unless you explicitly set model.allowRemote, because sending your source code off the machine should be a decision, not a default. An API key is read from the environment variable named by apiKeyEnv and never stored in the config file.

Generation is constrained to JSON: the two llama.cpp providers enforce a grammar, and because an arbitrary OpenAI-compatible server may ignore response_format entirely, responses are also unwrapped from markdown fences and surrounding prose before parsing. Repository text is treated as untrusted data throughout.

VS Code extension

npm run package:vscode
code --install-extension dist/react-surgeon-0.2.0.vsix
code examples/buggy-react-app

Alternatively use Extensions → … → Install from VSIX. Start the demo in a terminal, open the React Surgeon Activity Bar, choose Connect project, then Select UI element. Enter a bug description, choose Diagnose & Fix, and select an acceptance scenario when prompted. The panel also exposes verification, proof and X-Ray.

Workspace trust is required to execute project scripts. The extension needs Node and npm on the host.

The extension cannot use the bundled node-llama-cpp runtime. That package is a platform-specific native binary, so embedding it would make each VSIX installable on exactly one operating system. Inside VS Code, set model.provider to llama.cpp (a llama-server on PATH) or openai-compatible (an Ollama or LM Studio you already run). The CLI has no such limitation.

Use with your own React project

Install or link @react-surgeon/vite-plugin from this workspace, then add surgeon() before the React plugin:

import surgeon from "@react-surgeon/vite-plugin";
import react from "@vitejs/plugin-react";

export default defineConfig({
  plugins: [surgeon(), react()],
});

The plugin uses apply: 'serve', instruments intrinsic JSX elements only, and leaves production output untouched.

Then configure .react-surgeon/config.json for your app URL and, if 5173/18080/18081 are occupied, alternative ports. Provide a Vite dev script for automatic dev-server launch; otherwise start the app yourself. An existing user-owned server on the configured URL is always reused, never replaced.

Architecture

PackageRole
@react-surgeon/sharedShared types and schemas
@react-surgeon/coreAnalysis, agent, model provider, patching, verification, bridge
@react-surgeon/vite-pluginDev-only JSX source instrumentation and selection overlay
@react-surgeon/clireact-surgeon command line
packages/vscodeVS Code extension
examples/buggy-react-appThe demo lab — an ordinary React/Vite project

There is exactly one agent and one inference process, with browser verification serialized after inference shutdown.

The plugin runs before JSX compilation, and MagicString inserts source attributes into intrinsic JSX opening tags while preserving source maps. A shadow-root overlay highlights hovered nodes and intercepts the selection click. Metadata uses project-relative paths only. The bridge binds 127.0.0.1, validates the origin and a random session capability, and validates every received source path.

Babel extracts components, props, hooks, state, events, routes and ordinary relative imports; names and resolved imports determine likely parent relationships. This is deliberately conservative — higher-order wrappers and dynamic imports need better analysis in a later version.

ResourceManager shuts the model down before launching verification. Vite is started only if the configured URL is unavailable. A single Chromium instance is created per replay and closed in a finally. Process execution captures bounded output with timeouts, and Ctrl+C shuts down owned services.

See docs/ARCHITECTURE.md and docs/AGENT.md.

Security boundaries

Use React Surgeon only with trusted local projects. It is an MVP, not an operating-system sandbox. Full detail is in docs/SECURITY.md.

  • Filesystem. Model-facing paths are canonicalized inside the selected workspace. Absolute paths, traversal, escaping symlinks and junctions, .env variants, SSH material, common credential paths, node_modules and build output are blocked. Search skips symlinks and hidden directories. Patching is restricted to React source and CSS.
  • Execution. Only predetermined npm script categories run: typecheck, lint, test, build, and the Vite dev server. A package script can itself run arbitrary code, so an allowlisted name does not make an untrusted repository safe. VS Code workspace trust is required.
  • Network. The bridge and the model server both bind 127.0.0.1. Neither opens a firewall port. Playwright navigations and requests are restricted to localhost in a fresh non-persistent context; personal browser profiles and password stores are never opened.
  • Secrets. Session URLs contain a local capability — do not share them. The overlay strips it from the URL and keeps it in tab session storage. Context and command summaries redact common secret assignments and token patterns, but this is heuristic: do not put production credentials in source or scenarios.
  • Not exposed to the model. Commits, pushes, deployments, cloud calls, arbitrary shell tools, dependency installation and file deletion.

Core inference stays local. npm packages, browser binaries and GGUF weights are downloaded from their distribution servers; once cached, coding and verification need no hosted AI service. There is no telemetry.

Review every diff before committing or deploying.

Development

npm run typecheck
npm run lint
npm test
npm run build
npm run check:demo
npm run test:vsix
npm run test:integration
npm run package:vscode
ScriptWhat it does
buildEmits each workspace's dist entry with esbuild
typecheck / lint / testThe normal checks; unit tests use isolated temp folders and one Vitest worker to limit memory
check:demoFails if the demo app is not in its documented buggy state
reset:demoRestores the three demo defects from fixtures
test:vsixExtracts the VSIX and verifies isolated activation, commands, CSP and the packaged Chromium runtime
test:integrationFull end-to-end run against the real Qwen model
package:vscodeBundles the extension with esbuild and packages it with @vscode/vsce

npm install also installs the repository git hooks (core.hooksPath.githooks). The pre-commit hook runs check:demo so an agent's working-tree edits can never be committed into the demo lab — that would break npm run lint on a fresh clone and turn the lab into a no-op.

The integration test is not a mock: it exercises hover, selection, source mapping, a model-generated patch, static checks, browser assertions and proof, then restores the demo defect to keep the lab repeatable. Evidence is copied to dist/integration-proof.json.

The demo app has its own typecheck, lint, test and build scripts, so the agent verifies the target application rather than its own code.

Windows execution policies or managed sandboxes may block child processes, downloads or cache writes. Use the environment's explicit approval mechanism; do not bypass it.

External references: Vite plugin API · llama.cpp server · Playwright · VS Code extension API.

Limitations and troubleshooting

Limitations

  • This is an MVP, not production-grade autonomous repair. A 1.5B model can misdiagnose or exhaust the retry budget — in practice it solves the demo defects on the first cycle sometimes and fails entirely on others. That is exactly why verification, not the model, decides. Review every diff.
  • Analysis resolves ordinary relative imports. Aliases, higher-order wrappers, re-exports, dynamic routes and complex provider relationships may need manual context.
  • X-Ray is a text graph, and health output is a set of hints, not proofs.
  • npm is the supported command runner; other lockfile package managers are detected but not driven. Vitest and Jest are given unattended flags when needed.
  • Automatic acceptance generation is not implemented — supply explicit scenarios. The recorder does not yet capture every SPA history transition or full-page navigation across reloads.
  • model stop controls only an owned model start process in the same workspace; it never terminates arbitrary llama.cpp processes. Ctrl+C also shuts down owned processes.

Troubleshooting

SymptomFix
Blank overlay connectionNo active bridge, or the app was opened without the printed session URL. Restart start and use the new URL.
Missing Chromiumnpx playwright install chromium
Port conflictStop the owning process, or set another port in .react-surgeon/config.json
Model fails to startnpm run surgeon -- --verbose model setup; check PATH and free RAM. executable and modelPath accept absolute paths.
BLOCKED: Acceptance scenario already passesWorking as intended — your scenario does not reproduce the bug. Tighten the assertions.
Unverified edits left behindnpm run surgeon -- --project <path> undo

Future work: better alias and provider analysis, wider recorder coverage, automatic test proposals, a richer component tree, React Compiler diagnostics, and optional larger-model profiles. Verification reliability remains the priority.

License

MIT — see LICENSE.

Further reading: DEMO.md · architecture · agent design · security · development.

Languages

TypeScript

83.9%

JavaScript

15.5%