react-luau-doctor
v0.1.0
Published
Local React Lua and ReactRoblox diagnostics with coding-agent handoff
Maintainers
Readme
React Luau Doctor
Local, deterministic diagnostics for React Lua and ReactRoblox projects, with a first-class repair workflow for coding agents. The analyzer reports likely root causes and remediation steps; it never applies source patches itself.
Release identity: Written clearance for the development identity was not recorded, so this release is published as React Luau Doctor /
react-luau-doctor. It is not affiliated with or endorsed by Meta or the React team. See Meta's React trademark policy.
This Luau port is derived from millionco/react-doctor and preserves its diagnostic-report and coding-agent workflow for React Lua projects. The Rust analyzer, Luau semantic rules, and Luau fixtures are independently authored. The adapted CLI is pinned to React Doctor's last plain-MIT snapshot; see LICENSE, THIRD_PARTY_NOTICES.md, and the exact source record in PROVENANCE.md.
Install
The npm package requires Node.js 20 or newer and installs one platform-specific optional dependency:
npm install --save-dev react-luau-doctor
npx react-luau-doctor .On a fallback-branded release, use react-luau-doctor in both commands. Native
archives are also published for Windows, macOS, and glibc Linux on x64 and
ARM64. The npm launcher only selects an already-installed executable—it never
downloads a binary or runs an installer. In a source checkout it also recognizes
target/release and target/debug, which makes this development flow work:
cargo build --release
node bin/react-doctor-luau.js .Scan a project
# Full local scan; findings are advisory by default
react-luau-doctor .
# Show every rule and diagnostic location
react-luau-doctor . --verbose
# Only findings introduced relative to main, blocking on warnings
react-luau-doctor . --scope changed --base main --blocking warning
# Compare the staged index with HEAD
react-luau-doctor . --staged --scope changed
# Machine-readable report without a score
react-luau-doctor . --json-out report.json --no-scoreThe scan command accepts --scope full|files|changed|lines, --base <ref>,
--staged, repeatable --project <path>, --blocking none|warning|error,
--config, --score, --json, --json-out, --no-score, --no-warnings, and
--no-agent-prompt. Human output also accepts --verbose, --color
auto|always|never, --no-color, and --ascii. The default report mirrors
React Doctor's concise flow: it groups repeated findings, shows representative
sites for the top three error rules, summarizes every category, and finishes
with the doctor score card. Run react-luau-doctor --help for the complete
interface.
Interactive terminals receive the colorful report automatically. Redirected
output, CI, NO_COLOR, and TERM=dumb use an unstyled fallback; --color
always and --color never provide explicit control. JSON output never contains
terminal styling.
Additional commands are available for setup and reference:
react-luau-doctor init
react-luau-doctor rules list
react-luau-doctor rules explain react-luau/rules-of-hooks
react-luau-doctor why src/interface/Views/Hotbar.luau:203why <file>:<line>[:column] rescans the project and opens a focused,
syntax-colored explanation at that location: the triggering code, rule intent,
confidence, remediation, and a Luau before/after pattern.
Exit code 0 means the configured threshold was not reached, 1 means it was,
and 2 means analysis was incomplete or failed. A missing comparison base,
malformed configuration, or parser/analyzer failure is never presented as a
clean result.
Scope semantics
| Scope | Included diagnostics |
| --- | --- |
| full | Every finding in selected projects |
| files | Every finding in files changed from the base |
| changed | Findings introduced relative to the base |
| lines | Findings intersecting added or modified lines |
--staged reads index content and compares it with HEAD; it does not check out
or rewrite files. Baseline fingerprints tolerate line movement and renames while
distinguishing duplicate occurrences.
Repair with a coding agent
Coding-agent repair is a supported workflow, not an incidental use of JSON:
# Detect and launch Codex, Claude Code, or Cursor Agent
react-luau-doctor agent . --target auto
# Prepare a bundle and print its prompt without launching anything
react-luau-doctor agent . --target stdout --max-groups 3
# Install project-local guidance for selected agents
react-luau-doctor install --agents codex,claude,cursor --yes
# Hooks are always a separate, explicit choice
react-luau-doctor install --agents codex,claude --agent-hooks --yes
react-luau-doctor uninstall --agent-hooksEach handoff contains prompt.md, diagnostics.json, manifest.json, and a
short document for every included rule. Like React Doctor, the prompt selects
three priority rule groups and shows at most three representative locations per
group. fixGroupId remains in the full report so the agent can repair shared
root causes within each rule group. If agent launch or clipboard access fails,
the full prompt and bundle path are printed.
The handoff asks the agent to inspect and confirm each finding, preserve behavior,
make narrowly scoped changes, run the exact diagnostic command before and after
editing, and separate resolved, unresolved, and human-review work. Launches use
the agent's normal terminal, sandbox, and approvals. This project never passes
permission-bypass options such as --yolo, --dangerously-skip-permissions, or
equivalents.
With explicit hook consent, supported agents rerun a changed-scope warning scan after edit operations. Hooks have a 30-second timeout and fail open. Unsupported agents rely on the installed skill's mandatory final verification step.
Configuration and suppressions
The default project configuration is react-luau.toml. CLI flags override it.
include = ["src/**/*.luau", "src/**/*.lua"]
exclude = ["src/generated/**"]
react_modules = ["Packages.React"]
additional_hooks = ["useStore"]
effect_hooks = ["useTrackedEffect"]
blocking = "warning"
warnings = true
[rules]
"react-luau/exhaustive-deps" = "warning"
"react-luau/binding-getvalue-in-render" = "off"Narrow source suppressions are available when a diagnostic is intentionally not actionable:
-- react-luau-disable-next-line react-luau/exhaustive-deps
React.useEffect(function() doWork(value) end, {})
-- react-luau-disable react-luau/opaque-children
-- ...small intentional region...
-- react-luau-enable react-luau/opaque-childrenDefault rules
Rules are conservative and Luau-aware. A rule is enabled by default only after meeting the project's precision gate on independently written fixtures.
| Rule | Default | Detects |
| --- | --- | --- |
| react-luau/rules-of-hooks | error | Hooks in invalid control-flow or function contexts |
| react-luau/exhaustive-deps | warning | Proven captured reactive values missing from dependencies |
| react-luau/inline-dependencies | warning | Non-inline dependency tables |
| react-luau/set-state-in-render | error | Unconditional state updates during render |
| react-luau/immutable-props-state | error | Direct mutation of props or state |
| react-luau/binding-getvalue-in-render | warning | Proven binding :getValue() calls during render |
| react-luau/opaque-children | warning | Indexing, iterating, measuring, or mutating opaque children |
| react-luau/react-none-as-prop | warning | React.None used as an element prop |
| react-luau/multiple-key-sources | warning | Both a table key and an explicit key prop |
| react-luau/missing-list-key | warning | Array/numeric children without a stable key |
| react-luau/effect-connection-cleanup | warning | Proven effect-owned connections without cleanup |
| react-luau/unsupported-reactroblox-api | error | Calls to APIs omitted by ReactRoblox |
Rules follow the maintained React Lua repository's React API, Luau deviations, and ReactRoblox API.
Reports and score
JSON schema v1 includes scan mode and completeness, React detection, analyzed files/lines, structured failures, diagnostics and remediation metadata, summary, baseline delta, and score metadata. Reports are path-and-span sorted so identical inputs produce stable output even though files are analyzed concurrently.
The optional score comes from React Doctor's upstream score service; this port
does not invent or reproduce a local scoring formula. For each detected React
project, the CLI sends a gzip-compressed POST to
https://www.react.doctor/api/score. The payload is a sanitized finding
inventory containing only rule IDs, severities, static rule metadata, and fixed
protocol placeholders. It never contains source text, file paths, repository or
commit identity, fingerprints, enclosing symbols, or diagnostic-specific symbol
names.
The service owns the numeric score and label. Its weights and labels can change,
so the same finding inventory may receive a different result after an upstream
scoring update. The request times out after 10 seconds and fails open: diagnostics
and blocking behavior remain available, while the report marks only the score as
unavailable. --no-score disables every score and projection request and sends
nothing to the service.
For changed and lines scopes, scoring uses the project's finding inventory
before the scope's display filtering is applied. This keeps the project health
score separate from the smaller set of findings shown for the current change. A
multi-project report uses the lowest available project score. The concise human
report may make one optional second sanitized request with the displayed top
three error rules removed; that server-returned projection powers the ghost-gain
segment in the score bar. If the projection request fails, the primary score and
scan results remain unchanged.
Incomplete analysis, a non-React project, an explicit --no-score, and an
unreachable score service are reported as distinct score states. None is
presented as a locally calculated substitute.
GitHub Action
Released Action tags contain all six native binaries. The Action does not fetch
an executable at runtime. Replace OWNER and the version below with the actual
repository and release:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: OWNER/[email protected]
id: doctor
with:
directory: .
projects: |
client
shared
scope: changed
base: ${{ github.event.pull_request.base.sha }}
blocking: warning
comments: true
annotations: true
- run: echo "Score ${{ steps.doctor.outputs.score }}"The Action writes a job summary, emits up to 50 workflow annotations, and updates one marker-owned bot comment on pull requests when its token can write comments. Comment failure (common on untrusted fork PRs) is reported as a warning and does not hide the scanner result. Outputs include score, completeness, new/fixed/error/ warning counts, and a JSON array of affected files.
Privacy and safety
- Scanning, fingerprinting, baseline comparison, and prompt generation run locally.
- When scoring is enabled, the only network payload is the sanitized finding inventory described above. Source, paths, repository and commit identity, fingerprints, and symbols are never included.
--no-scoresends no score or projection request.- PR summaries contain diagnostic metadata, not source text.
- The analyzer suggests remediations but never writes patches.
- Source providers read the working tree, Git objects, or staged index without checking out a baseline.
- Dependency, generated,
.git,Packages,DevPackages,node_modules, andvendordirectories are excluded by default.
Parser compatibility
Version 0.1 pins full_moon 0.19 so the native crate remains buildable with the
declared Rust 1.71 toolchain. It supports typed Luau, generics, type packs,
compound assignments, interpolation, continue, if-expressions, and type
assertions. Luau syntax introduced after that parser release (for example
@native, user-defined type functions, leading union markers, or //=) is
reported as a structured parse failure: the report is marked incomplete and the
CLI exits 2. It is never treated as a clean scan. The parser is isolated behind
ParserBackend so a later release can raise this grammar ceiling without
coupling rules or source providers to one parser version.
Develop
cargo fmt --all -- --check
cargo test --all-targets --locked
npm test
cargo run -- . --scope full
# With cargo-fuzz installed on a supported nightly toolchain:
cargo fuzz run parse-and-analyzeRelease tags build Windows x64/ARM64, macOS x64/ARM64, and glibc Linux x64/ARM64.
Set the repository variable REACT_DOCTOR_LUAU_BRAND_CLEARED=true only after
written clearance is recorded; when it is absent or false, release packages and
commands are automatically rewritten to the fallback identity.
License
This project is MIT licensed. CLI presentation and workflow are adapted from
React Doctor commit 0c198589d81702cc0b59cfe6d41e63329154e203, the last
plain-MIT snapshot before upstream changed its license. The upstream copyright
and license are retained in LICENSE and
LICENSES/react-doctor-MIT.txt. Binary releases
also retain THIRD_PARTY_NOTICES.md, including the
MPL-2.0 notice for full_moon. Current Modified MIT React Doctor sources were
inspected only to document the score-service protocol; no source code from those
later revisions was copied into this project. See PROVENANCE.md
for both records.
