agent-doctor
Health checks for Effect TS codebases — react.doctor, but for Effect. Scans a repo, scores it 0–100, and reports Effect anti-patterns with file locations. Written in Rust on the oxc toolchain: ~40ms for 1,100 files, ~200ms for 1,800.
agent doctor v0.1.0
███████████████████████████░░░ 91/100 — Great
✖ require-yield-star error · Correctness · 2 issues
Inside Effect.gen, effects must be yielded with `yield*`. ...
src/program.ts:4:17 const value = yield Effect.succeed(1)
Usage
cargo build --release
agent-doctor <dir> # scan everything
agent-doctor <dir> --verbose --json # full report / machine-readable
agent-doctor --scope changed # only files changed vs main (PR mode)
agent-doctor --scope lines --base main # only issues on lines you touched
agent-doctor rules # list all 118 rules
agent-doctor explain no-map-returning-effect # why + how to rewrite it
agent-doctor rules --json # full catalog with rewrite recipes
agent-doctor --deep # merge type-aware @effect/language-service findings
agent-doctor --no-react # skip the React tier (on by default, see below)
agent-doctor lsp # run as a language server (editor diagnostics)
agent-doctor --adopt --scope lines # experimental: vanilla-TS → Effect migration
# recommendations, on exactly your PR's lines
agent-doctor --agent # experimental "agent doctor": flag the non-Effect
# slop LLM agents emit (if/else, ternaries, raw loops…)
agent-doctor --agent-strict # same, but escalate to errors and exit non-zero (CI gate)React tier — all of react-doctor, automatically
When agent-doctor detects a React project (a react dependency in package.json), it runs
react-doctor's full rule set automatically and merges its
findings into the report as rd/* rules in a React category — no flag or config needed.
This mirrors the --deep tier: agent-doctor orchestrates react-doctor, it doesn't reimplement
it. Install react-doctor so the tier can run (npm i -D react-doctor); a missing react-doctor
is a silent no-op. Opt out per-run with --no-react.
Configuration — agent-doctor.toml
Fastest path is the interactive setup — it detects your design system and the primitive UI libraries it wraps (Radix, Base UI, cva, …) and writes the config for you:
npx @jgalbsss/agent-doctor init # walkthrough; --yes to accept detected defaultsOr drop an agent-doctor.toml at the repo root by hand to pin the enforcement an agent must
follow, so the same standards apply no matter who runs the linter:
# default-on tiers — enable a tier for the whole repo without the CLI flag
[tiers]
agent = true # OOP→Effect + agent-hygiene + conventions always on
agent_strict = true # escalate those to errors (CI gate)
# react = false # opt out of the auto React tier
# per-rule control: "off" | "info" | "warn" | "error"
[rules]
no-explicit-any = "error"
agent-no-default-export = "off"
# enforce the design system: name the package, list the primitives it wraps.
# the component catalog is auto-discovered from the package's exports.
[design-system]
package = "@acme/ui"
forbid-import-prefixes = ["@radix-ui/", "class-variance-authority", "@mui/"]agent-doctor also inherits your TypeScript type setting: it reads tsconfig.json and, if
compilerOptions.strict isn't enabled, flags it (prefer-strict-tsconfig) — strict mode is
what makes every other type-safety rule load-bearing.
With [design-system] set, agent-doctor makes sure agents use your component library rather
than re-reaching for the primitives it wraps — import … from "@radix-ui/react-select" is
flagged in favor of @acme/ui/select (ds-no-banned-import). The catalog is read from the
package's exports, so there's nothing to keep in sync.
Claude Code plugin
This repo doubles as a Claude Code marketplace. Installing the plugin ships the agent-doctor skill so coding agents run the linter on their own TypeScript before committing. Full setup: docs/INTEGRATIONS.md.
Docs site
site/ is an Astro site rendering the full rule catalog with side-by-side bad→good
rewrites, search, and category filters. npm run gen regenerates its data from
agent-doctor rules --json; npm run dev to work on it locally.
Status
Early but real: 118 rules live across correctness, idiomatic, architecture,
performance, and v4-migration categories — every rule ships with a bad→good rewrite
recipe (explain), and 120+ integration tests cover the catalog (bad patterns fire,
clean code stays silent; example coverage is test-enforced). Rule sources: the Effect-TS
skills repo, the @effect/language-service diagnostic catalog, the effect-smol v4
MIGRATION guide, and the EffectPatterns community corpus (304 patterns). The full spec
is in docs/RULES.md; architecture and roadmap in
docs/ARCHITECTURE.md.
- Import-aware matching:
import { Effect as E } from "effect"andimport * as Effect from "effect/Effect"both resolve correctly. - Version-aware profiles: effect major detected from package.json; v4-migration rules
fire on v4 codebases automatically, or on v3 with
--migrate. - Test-file classification: findings in
*.test.ts/test/paths stay in the report but don't count toward the score (except test-specific rules). - Score model: penalty per distinct rule fired (errors 1.5, warnings 0.75), info rules never affect the score.
- Diff scoping:
--scope changed(files) /--scope linesvs--base(defaults to the merge-base with main) — untracked files count as fully changed. --deeptier: merges the ~78 type-aware diagnostics from@effect/language-service(its headlessdiagnostics --format jsonCLI) asls/*rules — we never reimplement type analysis.agent-doctor lsp: stdio language server publishing the syntactic rule set as editor diagnostics (full-sync; rule id as the diagnostic code).--adopt(experimental): flags vanilla TS that should migrate to Effect — async functions,.then()chains,new Promise,Promise.all, sequential awaits in loops — each with the clean Effect rewrite.prefer-foreach-over-yield-loop(yield loops inside Effect.gen →Effect.forEach) is always on as info.--agent(experimental, "agent doctor"): flags the non-Effect, non-functional patterns LLM agents reach for by default —if/elsechains, ternaries,x === "literal"guards, rawfor/whileloops,let/varmutation, inlineimport()/require(), reassignment / in-place payload mutation (intermediate states), and copy-pasted function bodies — each with the clean Effect/Match/combinator rewrite. Defaults towarn;--agent-strictescalates toerrorand exits non-zero so it can gate CI. It also runs a cross-file pass: a repo-wide index of named/bound functions flags ones that duplicate another by body (exact / fuzzy), name, or shape (params + call set) — so an agent reusing context sees "this helper already exists" instead of re-creating it. All duplicate/similarity findings stay info suggestions.- OOP → Effect (under
--agent): flags hand-rolled Gang-of-Four patterns that Effect replaces with a first-class primitive — Singleton →Context.Tag/Layer, Observer →PubSub/Stream, Strategy (single-method interface, ≥2 impls) → a function type, Visitor →Match.exhaustive, Chain of Responsibility →Effect.orElse/catchTag— each with the idiomatic rewrite. - Type safety (always-on): flags the escape hatches agents use to silence the compiler —
any, non-null!, double-casts (as unknown as), emptycatch {},@ts-ignore— plus per-function maintainability metrics (too many parameters, deep nesting, high cognitive complexity). Allwarn;--agent-strictescalates them to a hard CI gate. - Configuration:
agent-doctor.tomlpins per-rule severity (off/info/warn/error) and default-on tiers; agent-doctor also inherits the workspacetsconfig.jsonstrict setting. - Design system (opt-in,
[design-system]): make agents use the project's component library — flags imports of the raw primitives it wraps (@radix-ui/*→ the DS component). The catalog is auto-discovered from the package'sexports; no manifest to maintain. - Planned: suppression comments, editor extension packaging, agent handoff, prebuilt npm binaries for the remaining platforms (darwin-arm64 ships today).
Development
Requires rustc ≥ 1.94 (rust-toolchain.toml pins stable via rustup; if a Homebrew rust
shadows it, brew unlink rust or pass RUSTC=$HOME/.rustup/toolchains/<host>/bin/rustc).
Reference repos for rule development are expected (gitignored) under references/:
effect (v3), effect-v4 (effect-smol), skills, language-service, react-doctor.