feat(react): /react-doctor umbrella skill + in-house browser core + debug-agent debug job#853
feat(react): /react-doctor umbrella skill + in-house browser core + debug-agent debug job#853aidenybai wants to merge 10 commits into
Conversation
commit: |
|
React Doctor skipped this pull request — it changed no React files. Reviewed by React Doctor for commit |
f95f2cb to
b629b12
Compare
|
/rde parity |
|
❌ Parity failed — trace |
|
/rde parity |
… debug/perf runtime jobs Adds the @react-doctor/browser package (CDP attach-and-launch over playwright-core: open, eval, snapshot, screenshot, axe-core audit, console/network capture, LoAF-based perf with per-script attribution, single-load report, viewport emulation) and the React DevTools profiler harness injected as a document-start init script (window.__REACT_PERF__), the @react-doctor/debug NDJSON logging server for the debug job (per-project lock/log scoping, session dedup, daemon/json modes), the `browser` and `debug serve` CLI commands, and the expanded /react-doctor skill (perf, debug, design references) installed to both skills/ and .agents/skills/.
0e792bf to
a7d5a69
Compare
Adds the internal @react-doctor/mcp package and surfaces it as a `react-doctor
mcp` subcommand (bundled into the CLI and bin-fast-pathed like experimental-lsp,
since the stdio transport owns stdin/stdout). It exposes the skill's jobs as MCP
tools over stdio: doctor_scan (diagnose() score + diagnostics), the browser
tools (open with React profiler, eval, snapshot, screenshot, audit, console,
network, perf, report — each attaching a fresh CDP session per call), and the
debug log server (debug_serve / debug_read_logs / debug_clear_logs). Tools reuse
the existing @react-doctor/api and @react-doctor/browser/debug surfaces; failures
return isError results instead of throwing. Records one cli.invoked{command:mcp}
metric on start.
…on table The five read-only page tools (browser_audit/console/network/perf/report) shared the same url+connection+viewport input schema, read-only annotations, and runTool/withSession lifecycle. Register them through a small registerPageTool table so each is just its name, description, and a session action — no behavior change (verified by the tool-registration tests).
- security: validate the model-supplied debug endpoint is a loopback /ingest/<id> URL before debug_read_logs/debug_clear_logs fetch it, and bound those fetches with a timeout — closes a prompt-injection SSRF (incl. a destructive DELETE). - browser: BrowserSession.setViewport now holds its CDP session and clears the device-metrics override + detaches on dispose, so an emulated viewport can't linger on the persistent Chrome instead of relying on disconnect semantics. - privacy: scrub the user's home directory out of tool error messages (Chrome / profile / CDP paths) before they go back to the model. - docs: browser_audit/browser_report descriptions now state they reload the current page when no URL is given (use browser_perf to measure post-interaction).
Replace the CLI's inline { width; height } shape with @react-doctor/browser's
Viewport (type-only import, erased at build, so the lazy playwright-core boundary
is unaffected). Removes a duplicated type per AGENTS.md DRY.
…s review) - debug_serve rejects non-loopback hosts (loopback-only bind for the agent-driven path; CLI --host keeps its on-device flexibility) - debug_read_logs/clear_logs accept only endpoints debug_serve minted, replacing the loopback/path check with a stronger SSRF allowlist - debug server appendLog rejects JSON array bodies
Move the playwright-core and axe-core value imports behind dynamic import() inside @react-doctor/browser (load-playwright.ts + runAxe), so the barrel has no heavy static deps and consumers can import it statically. This dissolves the duplicated loadBrowser/isModuleNotFoundError dance in both the CLI and MCP (the missing-playwright hint now lives in one place) and lets parseViewport + MAX_VIEWPORT_PX live once in the browser package, reused by the CLI's Commander wrapper and the MCP tool. Bundle boundary preserved: axe-core splits into lazy chunks and playwright-core stays an external dynamic import — neither lands in the main cli.js startup path.
Record one `browser profile` pass that returns both a React render profile (slowest commits, hottest components by self time, unnecessary re-render counts) and a V8 CPU profile (functions ranked by self time, via the DevTools sampler over CDP) as JSON for agents to consume — no files written. Navigation runs before the profiler attaches so it binds to the final document's renderer; both lenses cover the post-load + interaction window. Surfaced through `BrowserSession.profile()`, the `browser_profile` MCP tool, and the `react-doctor browser profile` CLI command.
The pre-parse strip omitted --interaction, so its Playwright expression leaked in as a positional and `browser profile --interaction` failed with too-many-arguments. Add it to BROWSER_FLAG_SPEC with a regression test.
Remove two constant comments that just restated NAVIGATION_TIMEOUT_MS / LAUNCH_READY_TIMEOUT_MS, and a CLI comment that duplicated the openWithReactProfiler explanation already on the method it calls.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit c4e5658. Configure here.
| } finally { | ||
| await cdpSession.send("Profiler.disable").catch(() => {}); | ||
| await cdpSession.detach().catch(() => {}); | ||
| } |
There was a problem hiding this comment.
Profiler not stopped on failure
Medium Severity
In profile, when --interaction (or browser_profile’s interaction) throws, the finally block only calls CDP Profiler.disable and never Profiler.stop, and it never calls __REACT_PERF__.stop(). On the persistent Chrome session, a failed profile can leave the React and V8 profilers mid-recording and skew later browser profile runs until the page is reloaded.
Reviewed by Cursor Bugbot for commit c4e5658. Configure here.


Grows React Doctor from a code scanner into one skill,
/react-doctor, that helps the agent write better React, checks its own changes in the background, and can open a real browser to profile, debug, and review UI. Built to followdoc.md.What's here
/react-doctorumbrella skill — the existing scanner-only skill grows into one skill that routes across jobs. Ten always-on React baseline rules plus prose routing across doctor (static scan + 0–100 score, runs in the background), perf (React render + V8 CPU profiling), debug (reproduce in a real browser), design (measured UI review), and rule explain/configure. The command, package name, and brand stay React Doctor; the browser jobs only run when asked.@react-doctor/browser— a thinplaywright-corewrapper that attaches to a running Chrome over CDP (logins/cookies come along), launching its own persistent, detached Chrome only as a fallback so the page survives across CLI invocations.playwright-coreis an optional dependency and loaded lazily, so playwright never bundles into the CLI hot path. It also houses the React DevTools profiler harness insrc/react-profiler.react-doctor browsercommands:open/snapshot(accessibility tree) /screenshoteval— runs the expression with the Playwrightpagein scope, so an agent acts on whatsnapshotshowed using Playwright's own selectors (page.locator("text=Login").click(),page.getByRole(...)), withpage.evaluate(() => …)for raw DOM. No bespoke ref scheme.console(console + uncaught errors on load) /network(request waterfall, failures flagged)audit— axe-core accessibilityperf— long-animation-frame jank with per-script attribution, plus LCP/CLS. With a URL it measures a fresh load; with no URL it measures the live page without reloading, so jank from a precedingevalinteraction is captured.profile— one recording, two lenses: a React render profile (slowest commits, hottest components by self time, unnecessary re-render counts) and a Chrome DevTools CPU profile (V8 sampler over CDP, JS functions ranked by self time), returned as JSON for the agent — no files written. Pass--interaction '<playwright expr>'to record what it triggers; both lenses cover the post-load + interaction window.report— captures console, network, performance, and accessibility in a single page load (one navigation, not four), ~2.6x faster than running the focused commands separately.--cdp,--no-launch, and a one-shot--viewport WxHemulation.browser openinjects the React DevTools profiler aswindow.__REACT_PERF__;browser profileis the one-shot record + analysis, orbrowser evalcanstart()/stop()it manually and pull the raw DevTools export.react-doctor debug+@react-doctor/debug— a local NDJSON logging server (react-doctor debug serve, the default subcommand) the debug job posts runtime evidence to. Handles lock reuse, dedup, and daemon/--jsonmodes so the server outlives the spawning CLI process and the agent just gets back the endpoint.react-doctor mcp+@react-doctor/mcp— a Model Context Protocol server over stdio (the bin fast-pathsmcp) that exposes the doctor scan and the browser/debug jobs as MCP tools, so any MCP-capable agent can calldoctor_scan, thebrowser_*tools (includingbrowser_profile), and thedebug_*log server directly. Debug binds are loopback-only with a minted-endpoint allowlist.debugjob = debug-agent — the debug playbook runs the runtime-evidence loop: hypothesize → instrument with NDJSON logs (posted to the server above) → reproduce in the live browser → fix only with log proof → verify → clean up.references/{debug,design,performance,explain}.md, followed per job.Build / checks
build,typecheck,lint,formatgreen for@react-doctor/core,@react-doctor/browser,@react-doctor/debug, andreact-doctor.@react-doctor/browser,@react-doctor/debug,install-react-doctor,install-agent-hooks,parse-viewport, andstrip-unknown-cli-flagssuites pass; fullreact-doctorsuite green.dist/skills/react-doctor.main; repo-widetypecheckandlintare green.Deliberately deferred (follow-ups)
@react-doctor/browserand@react-doctor/debug(workspace deps today)product-thinkingpass for thebrowser/debugCLI surfaceNote
Medium Risk
Large new surface area (local Chrome launch, CDP attach, debug HTTP server, MCP stdio) with thoughtful guards, but agents can drive live browsers and post runtime logs—operational and prompt-injection considerations beyond the existing static scan.
Overview
Expands React Doctor from a static scanner into a
/react-doctorumbrella skill (v1.6) with ten always-on React writing rules, job routing (doctor / perf / debug / design), and new playbooks for browser-backed perf, debug-agent, design, and rule explain flows.Adds
@react-doctor/browser: CDP attach (optional persistent Chrome launch),BrowserSessionfor open/eval/snapshot/screenshot, axe audits, console/network capture, LoAF/LCP/CLS perf, combined profile (React DevTools export analysis + V8 CPU self-time), and one-load report. Ships a document-start React profiler inject and CLIreact-doctor browsersubcommands with--cdp,--no-launch, and--viewport.Adds
@react-doctor/debugandreact-doctor debug serve(daemon/--json, project-scoped lock reuse) for NDJSON runtime logs, plusreact-doctor mcpvia@react-doctor/mcp(doctor_scan,browser_*,debug_*tools; loopback-only debug bind and minted-endpoint allowlist). The bin fast-pathsmcplike the LSP entry; build copies the profiler inject asset into the published CLI bundle;playwright-coreis optional and lazy-loaded.Reviewed by Cursor Bugbot for commit c4e5658. Bugbot is set up for automated code reviews on this repo. Configure here.