pyreon

@pyreon/cli is the command-line companion for Pyreon projects. It ships the pyreon binary with these commands: pyreon check — a fast, file-scoped anti-pattern scan with inline fixes; pyreon plain — a Plain-Mode readiness report and classic→plain codemod; pyreon add — installs @pyreon/* packages and prints a tailored setup recipe; pyreon new — scaffolds a new project; pyreon mcp / pyreon atlas / pyreon loom / pyreon lathe — thin front doors to the MCP server, the Atlas component workbench, the Loom dependency observatory, and the Lathe spec-to-client generator; pyreon doctor — a project-wide health audit that runs a battery of independent gates in parallel, aggregates every finding into a unified report, and computes a 0-100 health score with a letter grade and a per-category bar chart; pyreon context — a project-structure scanner that writes a machine-readable summary for AI coding assistants; pyreon info — an environment report that lists every installed @pyreon/* version and flags version skew before it trips the duplicate-instance guard; pyreon upgrade — the fix for that skew, aligning every @pyreon/* dependency to one version; and pyreon lint — a thin front door to @pyreon/lint that forwards every pyreon-lint flag.

@pyreon/clistable

Installation

npm install -D @pyreon/cli
bun add -d @pyreon/cli
pnpm add -D @pyreon/cli
yarn add -D @pyreon/cli

The package installs a single pyreon binary. Run it through your package manager's runner without a global install:

npx pyreon doctor
bunx pyreon doctor
pnpm exec pyreon doctor
yarn pyreon doctor

@pyreon/cli declares typescript as a peer dependency (>=5.0.0) because the audit gates lean on TypeScript's compiler API for AST analysis. It works in any project — the gates that target Pyreon-specific surfaces (SSG conventions, islands, doc claims) skip gracefully when the project isn't a Pyreon repo.

Why a doctor command?

Pyreon's quality gates have always existed as a sprawl of standalone scripts — check-distribution, check-doc-claims, audit-types, audit-tests, the islands/SSG/content auditors. Each prints its own format, exits its own way, and you have to remember to run all of them. pyreon doctor is the single entry point: one command runs every gate, isolates failures (a gate that throws never takes down the rest), and rolls the results into one scored report you can read at a glance — or pipe into CI, GitHub Actions, or an AI agent.

The output is modeled after react.doctor: a big score banner, per-category bars, and the top findings with actionable fixes inline.

pyreon doctor
  pyreon doctor · project health audit

  Score:  92/100   Grade: A

  Per category:

  correctness    █████████░░░ 87  · 1E 1W
  performance    ████████████ 100 · clean
  architecture   ████████████ 100 · clean
  testing        ████████████ 100 · clean
  documentation  ████████████ 100 · clean
  best-practices clean (advisory — not graded)

  Top findings (2 of 2):

  ✗ useState imported from React. Use signal() from @pyreon/reactivity.  react-patterns/no-usestate
     src/App.tsx:1:9
     fix: import { signal } from "@pyreon/reactivity"

  ! className → class (HTML standard attribute).  react-patterns/className
     src/App.tsx:3:18

  1 error · 1 warning · 13 gates · 1.4s

By default pyreon doctor runs the 13 fast gates (~2-5s on a warm cache). The 2 slow gates are opt-in via --full.

pyreon check

A fast, file-scoped scan for Pyreon/React anti-patterns — the terminal-native twin of the MCP validate tool. It runs the compiler's two static detectors (detectPyreonPatterns for "using Pyreon wrong" footguns, detectReactPatterns for "from React" mistakes) over source files and prints each finding with its inline fix. It exits non-zero on any finding, so it doubles as a pre-commit / CI gate.

pyreon check                    # scan git-changed .ts/.tsx files (the pre-commit inner loop)
pyreon check src/              # scan a directory (recursive; skips node_modules/lib/dist)
pyreon check src/App.tsx        # scan specific files
pyreon check --fix              # apply the mechanically-safe auto-fixes in place
pyreon check --json             # machine-readable findings for tooling

Example output:

  src/Counter.tsx
    4:2  [signal-write-as-call] (auto-fixable — run with --fix)
      `count(value)` does NOT write the signal — use `count.set(value)` …
      → count.set(5)
    5:9  [for-missing-by]
      <For each={...}> requires a `by` prop so the keyed reconciler can preserve identity …
      → <For each={items} by={(item) => item.id}>

  2 findings in 1 file  ·  some are auto-fixable with --fix

How it differs from the neighbours: pyreon doctor is the whole-project health audit (all gates + a 0-100 score, slower); pyreon lint runs the @pyreon/lint rule set (a different engine). pyreon check is the fast, file-scoped compiler-detector pass — the quickest way to answer "did I introduce a Pyreon anti-pattern in this file / my diff?" --fix applies migratePyreonCode + migrateReactCode (only the mechanically-safe fixes; anything else stays reported so it still gates).

pyreon add

Install one or more @pyreon/* packages and print exactly how to wire each one in — the root provider to add plus a usage snippet, tailored per package.

pyreon add query              # installs @pyreon/query (bare name → @pyreon/query)
pyreon add query toast i18n   # several at once
pyreon add @pyreon/form --dry-run   # show the plan + recipe without installing
pyreon add query --json       # machine-readable plan

It auto-detects your package manager from the lockfile (bun.lock → bun, pnpm-lock.yaml → pnpm, yarn.lock → yarn, else npm), walking up from the current directory — so you don't have to remember which add command this project uses. Then it prints the setup:

  @pyreon/query — TanStack Query adapter …
  1. Add the provider near your app root:
      import { QueryClient, QueryClientProvider } from '@pyreon/query'
      const queryClient = new QueryClient()
      <QueryClientProvider client={queryClient}> <App /> </QueryClientProvider>
  2. Use it:
      const todos = useQuery(() => ({ queryKey: ['todos', id()], queryFn: fetchTodos }))
  Docs: https://pyreon.dev/docs/query

Curated recipes exist for the flagship packages (query, toast, i18n, permissions, form, store, router, head) plus the three dev-tool packages that have a command, not a root provider, to reach for (loom, atlas, config — usage is the command you run, not JSX to wrap); any other @pyreon/* package still installs, with a generic docs pointer. The recipes live in the CLI itself (hand-authored + verified against each package's real API) rather than being generated from manifests — published packages don't ship their manifests, and reaching across packages for them would bloat the CLI's install.

pyreon new

Scaffold a new Pyreon project — a single front door alongside the rest of the pyreon commands, instead of a separately-remembered npm create @pyreon/zero.

pyreon new my-app              # web / full-stack app (delegates to @pyreon/create-zero)
pyreon new my-app --native     # multiplatform (SwiftUI + Compose) via @pyreon/create-multiplatform
pyreon new my-app --template blog   # any other flag passes straight through
pyreon new my-app --dry-run    # print the npx command without running it

It's a thin, dependency-free delegator: it npx-runs the appropriate create-* scaffolder (which owns the interactive prompts and templates), pinned to @latest so a new project always starts on the freshest templates regardless of how old your globally-installed @pyreon/cli is. Your project name and any extra flags pass through untouched.

pyreon mcp

Launch the Pyreon MCP server — the Model Context Protocol server (@pyreon/mcp) that serves Pyreon's API reference, pattern catalog, validate, diagnose, and changelog to AI coding assistants. A single front door alongside the other pyreon commands, instead of a separately-remembered package invocation.

pyreon mcp                 # launch the stdio MCP server (blocks; the client talks over stdin/stdout)
pyreon mcp --dry-run       # print the npx command without launching
pyreon mcp <args>          # any extra flags pass straight through to the server

It's a thin, dependency-free delegator: it npx-runs @pyreon/mcp. Unlike pyreon new, it is deliberately not pinned to @latest — npx @pyreon/mcp prefers the project-local @pyreon/mcp when it's installed, so the served API reference matches your installed Pyreon version, and only fetches it on demand when the project doesn't have it. It inherits stdio, so the AI client that spawned pyreon mcp communicates with the server directly.

Point your assistant's MCP config at pyreon mcp (e.g. as the command) to get Pyreon-aware validate / get_api / get_pattern / diagnose tools.

pyreon atlas

pyreon atlas init .              # detect this workspace's packages and write pyreon.config.ts
pyreon atlas scan .              # derive + verify the component catalog (exits non-zero on failing scenarios)
pyreon atlas dev .               # serve the Atlas workbench for this project
pyreon atlas verify <Component>  # re-check ONE component; reports WHICH check failed
pyreon atlas check <Component> '{"state":"primary"}'  # validate a proposed usage against the derived contract
pyreon atlas verify-browser .    # browser-half verification: reactive coverage + visual snapshots
pyreon atlas build .             # compile the workbench into a static, deployable site
pyreon atlas --dry-run <args>    # print the npx command without launching

The Atlas component workbench from the CLI front door. A thin, dependency-free delegator to @pyreon/atlas — every arg after atlas passes straight through, so pyreon atlas --help prints Atlas's own (much larger) flag reference for every subcommand. Like pyreon mcp it is deliberately not pinned to @latest: the project-local @pyreon/atlas wins when installed, so the derived catalog matches your installed Pyreon version.

pyreon loom

pyreon loom scan .                # analyze the dependency fabric; red exit on error-severity findings
pyreon loom scan . --strict       # also exit non-zero on warnings
pyreon loom scan . --json         # the full report as JSON on stdout (valid to redirect to a file)
pyreon loom dev .                 # serve the observatory UI (graph / matrix / cycles / impact / manifests)
pyreon loom build .               # prerender the observatory to a standalone static site
pyreon loom --dry-run <args>      # print the npx command without launching

The Loom dependency observatory from the CLI front door. A thin, dependency-free delegator to @pyreon/loom — every arg after loom passes straight through. scan needs no extra dependencies; dev/build need vite + @pyreon/vite-plugin (and build additionally @pyreon/zero) as optional peers. Not pinned to @latest — the project-local @pyreon/loom wins when installed.

pyreon lathe

pyreon lathe generate ./openapi.yaml    # read a spec, write the @pyreon/http + @pyreon/query client
pyreon lathe check ./openapi.yaml       # regenerate in memory; fail (exit 1) if the committed output is stale
pyreon lathe pull <url>                 # fetch a remote spec to the configured input path
pyreon lathe generate --target multiplatform   # also emit + verify native (Swift/Kotlin) modules
pyreon lathe --dry-run <args>           # print the npx command without launching

The Lathe spec-to-client generator from the CLI front door. A thin, dependency-free delegator to @pyreon/lathe — every arg after lathe passes straight through. Deliberately not pinned to @latest (same reasoning as pyreon mcp/pyreon atlas/pyreon loom): the generator that runs is the one pinned alongside the app it generates for, so a newer generator never silently emits against an older runtime.

pyreon plain

A readiness report for Plain Mode — Pyreon's compile-time dialect that lets reactive code read as plain JavaScript (let count = state(0) instead of const count = signal(0)). Dry-run (the default) is per-file: does it already compile clean under plain, convert fully, convert partially with every declined shape named, or have nothing to convert? --write applies the classic → plain codemod in place. Test and spec files are skipped unless --include-tests is passed (test runners often run without the pyreon() plugin, where plain code cannot compile).

pyreon plain                     # scan the whole tree under cwd (a readiness question is project-level)
pyreon plain src/                # scope to a directory
pyreon plain src/Counter.tsx     # scope to one file
pyreon plain --json              # machine-readable report
pyreon plain --write             # apply the codemod to every fully-convertible binding

Example output:

  Plain Mode readiness

  src/Counter.tsx ✓ converts fully (2 bindings)
  src/Editor.tsx ◐ partial (1 convert, 1 declined)
      12:9 [deep-mutation-on-shallow] .push on a state.raw()/non-literal binding needs a manual rewrite

  2 file(s) scanned · 1 already plain · 2 convertible · 0 declined · 0 nothing to convert

  Declined shapes (build-next histogram):
    deep-mutation-on-shallow  1

  Run `pyreon plain --write` to apply.

Conversion is per-binding: a let/const converts only when every reference to it has a plain form (calls, .set, a simple .update, .peek → untrack); anything else stays byte-untouched with a named reason, and an object/array-literal signal becomes state.raw(...) rather than changing semantics. With no path arguments it scans the whole project (unlike pyreon check, whose no-arg default is git-changed files — a readiness report is a project-wide question, not a per-diff one). The declined-shape histogram at the bottom tells you which rewrite is worth building next if a lot of your codebase declines for the same reason.

FlagEffect
--writeApply the codemod to every fully-convertible binding, in place.
--jsonMachine-readable report (files, summary, declinedHistogram).
--include-testsAlso scan test and spec files (skipped by default).

pyreon doctor

How it works

The orchestrator selects a gate set, runs every gate concurrently via Promise.all, and hands the results to the aggregator. The gates are fully independent — no shared state, no file-write contention (only --fix writes, and the writing gates target disjoint file patterns) — so running them in parallel cuts the fast set from ~5s sequential to ~1-2s wall-clock.

Each gate emits findings in a unified shape: a category (correctness / performance / architecture / testing / documentation / best-practices), a severity (error / warning / info), a stable <gate>/<rule> code, a human message, an optional source location, and an optional fix hint. The aggregator sorts findings (errors first, then by category, then by code), tallies totals, and computes the score.

Scan scope — workspace roots

The file-scanning gates (react-patterns, pyreon-patterns, lint, audit-tests) resolve their scan scope from the workspace's own configuration — the root package.json workspaces globs or pnpm-workspace.yaml — discovered by walking up from the cwd. Multi-root layouts (apps/* + packages/* + modules/*) are fully covered, and results are identical from any directory in the repo; a repo with no workspaces is treated as a single package. Per package, src/** is scanned when present, always minus tests / fixtures / .d.ts (detector fixtures deliberately contain anti-patterns — scoring them would produce a false grade).

The report header shows what was scanned (Scope: 4 package root(s) from workspaces (…) plus per-gate file counts), and a gate that matches no files is skipped with a warning instead of reading as a clean pass — its category is "not measured" and never contributes a free 100. If nothing at all was measured, the score renders as — and --ci exits non-zero. Escape hatches: --roots <glob,...> overrides discovery for non-standard layouts, and pyreon.doctor.excludeRoots globs in the root package.json exclude demo/docs workspaces from grading.

Gates

There are 15 gates total: 13 fast (run by default) and 2 slow (opt in with --full).

GateSpeedCategoryWhat it checks
react-patternsfastcorrectness"Coming from React" mistakes — useState, useEffect, className, htmlFor, onChange on inputs, .value writes, React-package imports. *-compat packages are excluded (exposing useState/className is their literal purpose).
pyreon-patternsfastcorrectness"Using Pyreon wrong" mistakes — <For> missing by, destructured props, signal-write-as-call, and more (the [detector: …] anti-pattern codes). Codes a @pyreon/lint rule fully owns (process-dev-gate, raw-add-event-listener, query-options-as-function) are deferred to the lint gate so they aren't double-reported at the wrong severity.
lintfastby ruleEvery configured @pyreon/lint rule across first-party source (reads your .pyreonlintrc.json). Each finding's category is derived from the rule — performance rules land in performance, architecture/ssr/ssg/router/styling/accessibility in architecture, the rest in correctness. Opt-in best-practice rules (meta.optIn) route to the advisory best-practices bucket.
distributionfastarchitecturePublished-package hygiene — every @pyreon/* package declares sideEffects, and its files array does NOT exclude !lib/**/*.map (source maps must ship so framework stack traces are readable). Includes a live npm pack --dry-run probe of @pyreon/reactivity confirming .map files land in the tarball.
doc-claimsfastdocumentationHand-quoted numeric claims in docs match the source of truth — hook-export count, doc-page count, lint-rule count, lint-category count, detector-code count. Catches the drift where one claim site is bumped and the others aren't.
islands-auditfastarchitectureCross-file island detectors (same scanner as the MCP audit_islands tool): duplicate-name, never-with-registry-entry, registry-mismatch, nested-island, dead-island.
ssg-auditfastarchitectureSSG / ISR convention checker — _404.tsx placement (404-outside-layout-dir), dynamic routes missing getStaticPaths (warn — legit under mode: 'ssr'/'isr'), non-literal revalidate exports.
content-auditfastarchitecture@pyreon/zero-content content — missing-frontmatter-title, broken-internal-link, orphaned-md-file. Validated per-config so two apps that both mount /docs don't cross-contaminate.
native-auditfastarchitectureMultiplatform (PMTC) build hazards in .tsx files importing @pyreon/primitives — web-only-package-import (a package that can't render on native) and native-unsupported-decl (a top-level interface/enum/class PMTC silently drops). Both warnings — they don't break the web build. The audit lives in the optional peer @pyreon/native-compiler (@pyreon/native-compiler/audit) and is loaded lazily: skips with an install hint when it is absent, and skips when no @pyreon/primitives importer is found.
audit-testsfasttestingThe mock-vnode anti-pattern — tests that build { type, props, children } literals (or a vnode() helper) instead of going through real h(). Classifies each file HIGH / MEDIUM / LOW.
check-dedupfastarchitectureDuplicate @pyreon/* versions in the lockfile (bun.lock / package-lock.json / pnpm-lock.yaml). Multiple module instances of one Pyreon package break framework contracts; this catches it statically before the runtime sentinel throws.
dependency-fabricfastarchitectureThe workspace's dependency fabric, from @pyreon/loom — phantom deps, version drift, runtime cycles, prod-imports-of-dev-deps, peer mismatches. Runs the project's own installed loom (resolved through your node_modules, never fetched) and skips when it isn't installed, so doctor never installs anything mid-audit. Loom's severities are carried through unchanged — unused-dep stays info because it is lexical evidence, not proof. The scan uses --no-write: auditing your repo must not leave a loom-report.json in it.
audit-leak-classesfastbest-practices (advisory)The memory-leak class heuristics (Class A / C / D / I). Advisory — false-positive-prone, so findings are VISIBLE but excluded from the grade and from --ci.
audit-typesslowarchitectureTyped-but-unimplemented public fields — interface fields with zero non-type references in their owning package (the mode: "ssg" typed-but-never-read bug class). Requires --full.
bundle-budgetsslowperformanceEach published package's gzipped main-entry size against its locked budget (over-budget), packages missing a budget entry (missing-budget), and packages the bundler couldn't measure (bundle-failed). Requires --full.

When a gate is excluded (slow gates without --full, or --skip/--only filtering), it appears in the report's skipped footer with the reason — enable with --full for slow gates, skipped for user-filtered ones. Some gates self-skip when their target surface is absent: native-audit (@pyreon/native-compiler not installed, or no @pyreon/primitives importer), audit-types (no scripts/audit-types.ts, i.e. outside the Pyreon monorepo).

The score

The score is a simple mean of the included category subscores. A category with no contributing gate is excluded from the mean rather than counted as a misleading 100 — an unmeasured category shouldn't pull the average up.

Per-finding penalty by severity:

SeverityPenalty
error10 points
warning3 points
info1 point

Each category subscore is 100 - clamp(sum of penalties, 0, 100), so 10 errors saturate a category to 0, 33 warnings cost 99 points, and so on. The mental math is predictable by design: fix one error, gain 10 points (capped at 100).

The best-practices category is advisory — it is scored and displayed (so opt-in best-practice findings stay visible), but it is NEVER folded into the overall mean or grade, and its errors never fail --ci. Opinionated rules shouldn't tank an objective health score.

Letter grades:

GradeScore
A90-100
B80-89
C70-79
D60-69
Fbelow 60

Flags

FlagEffect
--fullInclude the slow gates (audit-types, bundle-budgets).
--fixAuto-fix what's fixable — lint rules with auto-fixers + react-patterns migrations. Writes to disk; applied fixes surface as info findings, residual issues stay as warnings/errors.
--only <gates>Run ONLY the listed comma-separated gates. Takes precedence over --full and the default set.
--skip <gates>Exclude the listed comma-separated gates (applied after --only).
--format <text|json|gha>Output format (default: text).
--jsonShortcut for --format=json — emits the full DoctorReport.
--ghaShortcut for --format=gha — GitHub Actions annotation lines.
--ciExit non-zero on error findings only (advisory best-practices errors excluded; warnings/info never fail).
--audit-min-risk <high|medium|low>Minimum risk floor for the audit-tests gate (default: medium).
--roots <globs>Comma-separated globs (relative to cwd) that replace workspace-root discovery for the file-scanning gates.
--help, -hPrint usage.
--version, -vPrint the CLI version.

Valid gate names for --only / --skip: react-patterns, pyreon-patterns, lint, distribution, doc-claims, islands-audit, dependency-fabric, ssg-audit, content-audit, native-audit, audit-tests, check-dedup, audit-leak-classes, audit-types, bundle-budgets. An unknown gate name is rejected with an error listing the valid set.

pyreon doctor                                  # 13 fast gates + score
pyreon doctor --full                           # add audit-types + bundle-budgets (15 gates)
pyreon doctor --only lint,react-patterns       # run a focused subset
pyreon doctor --skip doc-claims                # everything except doc-claims
pyreon doctor --only audit-types --full        # one slow gate in isolation
pyreon doctor --fix                            # auto-fix lint + react-patterns
pyreon doctor --ci                             # CI gate — exit non-zero on errors
pyreon doctor --json                           # full report for AI agents / dashboards
pyreon doctor --gha                            # GitHub Actions inline annotations
pyreon doctor --audit-min-risk high            # only HIGH-risk test-env findings

Output formats

Text (default)

The human-readable renderer (--format=text, the default) prints, in order:

  1. Banner — pyreon doctor · project health audit + Score: N/100 Grade: X.

  2. Per-category bars — a 12-cell bar per category, colored by grade, with an E/W/i breakdown (1E 2W 3i) or clean. The advisory best-practices row is labeled advisory — excluded from grade & --ci so it's never mistaken for a grade-affecting bucket. A category with no contributing gate shows skipped.

  3. Top findings — up to 10 findings, each with a severity glyph (✗ error, ! warning, ℹ info), the bolded message, the <gate>/<rule> code, a clickable file location, related locations (↳), and a fix: hint. A …and N more line points to --json for the full list.

  4. Footer — skipped gates with reasons, total counts, gate count, and elapsed wall-clock.

Colors degrade gracefully via @pyreon/ansi (respects NO_COLOR / FORCE_COLOR / TTY detection), and file paths are OSC-8 hyperlinks in terminals that support them (iTerm2, WezTerm, kitty, VS Code).

JSON (--json / --format=json)

Emits the full DoctorReport as pretty-printed JSON — the stable shape consumed by AI agents, CI dashboards, and third-party tooling.

pyreon doctor --json
{
  "score": 92,
  "grade": "A",
  "categories": [
    { "category": "correctness", "score": 87, "errors": 1, "warnings": 1, "infos": 0, "grade": "B", "included": true },
    { "category": "performance", "score": 100, "errors": 0, "warnings": 0, "infos": 0, "grade": "A", "included": false },
    { "category": "architecture", "score": 100, "errors": 0, "warnings": 0, "infos": 0, "grade": "A", "included": true },
    { "category": "testing", "score": 100, "errors": 0, "warnings": 0, "infos": 0, "grade": "A", "included": true },
    { "category": "documentation", "score": 100, "errors": 0, "warnings": 0, "infos": 0, "grade": "A", "included": true },
    { "category": "best-practices", "score": 100, "errors": 0, "warnings": 0, "infos": 0, "grade": "A", "included": false }
  ],
  "gates": [
    {
      "gate": "react-patterns",
      "category": "correctness",
      "findings": [
        {
          "category": "correctness",
          "severity": "error",
          "code": "react-patterns/no-usestate",
          "gate": "react-patterns",
          "message": "useState imported from React. Use signal() from @pyreon/reactivity.",
          "location": { "path": "/abs/src/App.tsx", "relPath": "src/App.tsx", "line": 1, "column": 9 },
          "fix": "import { signal } from \"@pyreon/reactivity\"",
          "fixable": false
        }
      ],
      "meta": { "scanned": 42, "elapsedMs": 31 }
    }
  ],
  "findings": [ "…flat list, ordered errors → warnings → info" ],
  "totals": { "errors": 1, "warnings": 1, "infos": 0 },
  "elapsedMs": 1402,
  "timestamp": "2026-06-24T10:00:00.000Z"
}

Top-level keys: score, grade, categories (always 6 entries, included flags coverage), gates (every gate that ran or was skipped, with meta), findings (the flat sorted list), totals, elapsedMs (wall-clock), and timestamp (ISO, for diffing runs).

GitHub Actions (--gha / --format=gha)

Emits one ::error / ::warning / ::notice workflow-command line per finding, which GitHub renders as inline annotations in the PR's "Files changed" tab. Doctor error → GHA error, warning → warning, info → notice. A summary ::notice:: header carries the score + totals.

pyreon doctor --gha
::notice::pyreon doctor score: 92/100 (A) — 1 errors, 1 warnings, 0 info
::error title=react-patterns%2Fno-usestate,file=src%2FApp.tsx,line=1,col=9::useState imported from React. Use signal() from @pyreon/reactivity. — import { signal } from "@pyreon/reactivity"
::warning title=react-patterns%2FclassName,file=src%2FApp.tsx,line=3,col=18::className → class (HTML standard attribute).

Property values (file=, title=) are URL-encoded so commas and colons in paths don't truncate the annotation.

CI usage

The canonical CI shape is pyreon doctor --ci (exit non-zero on errors) combined with --gha for inline PR annotations:

# .github/workflows/health.yml
name: Health
on: [pull_request]
jobs:
  doctor:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: oven-sh/setup-bun@v2
      - run: bun install
      # Inline annotations on the PR (always exits 0 in this mode unless --ci added)
      - run: bunx pyreon doctor --gha
      # The actual gate — fails the job on any error finding
      - run: bunx pyreon doctor --ci

--ci and --gha/--json are independent: --ci controls the exit code, the format flags control the output. To both annotate and gate in one run, combine them — pyreon doctor --gha --ci prints GHA annotations and exits non-zero on errors.

For a focused gate in CI (e.g. only the islands audit on a PR that touched island code):

pyreon doctor --only islands-audit --ci

Legacy flags

The pre-v2 single-purpose flags still work — they map to --only <gate> shortcuts so existing CI scripts keep functioning unchanged:

Legacy flagEquivalent
--audit-tests--only audit-tests
--check-islands--only islands-audit
--check-ssg--only ssg-audit
--check-content--only content-audit
--check-native--only native-audit
pyreon doctor --check-islands                          # == --only islands-audit
pyreon doctor --audit-tests --audit-min-risk high      # filtered test-env audit
pyreon doctor --check-ssg --json                       # SSG audit, machine-readable
pyreon doctor --check-native                           # multiplatform PMTC hazards

When you pass multiple legacy flags together, they compose into a single --only set (e.g. --check-islands --check-ssg runs both). An explicit --only always wins over the legacy flags.

Gate deep-dives

A few gates are worth understanding in detail.

islands-audit

The same scanner as the MCP audit_islands tool. It flags:

  • duplicate-name (error) — two island() calls with the same name; only the first hydrates, the rest silently fail.

  • never-with-registry-entry (error) — a hydrate: 'never' island paired with a manual hydrateIslands({ … }) registry entry (which pulls the component into the client bundle, defeating the zero-JS strategy).

  • registry-mismatch (error) — a hydrateIslands({ X }) entry where X has no matching island() declaration (typo / removed island).

  • nested-island (error) — an island() whose loader-target file also contains an island() (the outer's hydration replaces the inner subtree before its loader runs).

  • dead-island (warning) — an island() no other source imports (declared but never wired up — warning because it might be intentional mid-refactor).

audit-tests

Scans every *.test.{ts,tsx} for the mock-vnode anti-pattern — tests that hand-construct { type, props, children } literals instead of going through real h() from @pyreon/core. Mock-only tests pass while the real rendering pipeline stays unexercised, which is exactly how a silent metadata-drop bug shipped once. Each file is classified HIGH / MEDIUM / LOW from the balance of mock-literal count, helper-definition count, helper-call count, and real-h()-call count. --audit-min-risk sets the floor (default medium, so LOW findings are hidden unless you pass --audit-min-risk low). Risk → severity: HIGH = error, MEDIUM = warning, LOW = info.

check-dedup

Parses bun.lock, package-lock.json, or pnpm-lock.yaml (whichever exists) and flags any @pyreon/* package resolving to more than one version. Two instances of the same Pyreon package break framework contracts — signal tracking, lifecycle hooks, context propagation — and the runtime singleton sentinel throws on first dual-load. This gate catches the installed-state mismatch statically, before deploy. The fix is to pin a single version and remove the conflicting transitive entries.

audit-types

A --full gate that walks every exported interface in the high-risk Pyreon packages (zero, router, core, server, runtime-server, vite-plugin) and counts non-type references. A public field with zero non-type references is the typed-but-unimplemented bug class — a config option that's documented and typed but the runtime never reads. It maps HIGH → error, MEDIUM → warning, LOW → info. Outside the Pyreon monorepo (no scripts/audit-types.ts) it skips gracefully.

pyreon context

The context command scans your project's structure and writes a machine-readable summary to .pyreon/context.json, designed to be fed to AI coding assistants and consumed by @pyreon/mcp.

pyreon context                          # writes .pyreon/context.json
pyreon context --out path/to/ctx.json   # custom output path

The scan (delegated to @pyreon/compiler's project scanner) detects:

  • Components — your Pyreon components.

  • Routes — file-system routes.

  • Islands — island() declarations.

It writes the JSON, prints a summary, and ensures .pyreon/ is in your .gitignore (the directory is a generated artifact, not source). On success:

  ✓ Generated .pyreon/context.json (42 components, 7 routes, 3 islands)

The output JSON is stable and pipeable — drop it into an LLM prompt for project-aware assistance, or let @pyreon/mcp read it to answer questions about your codebase. Re-run it after structural changes (new routes, new islands) to refresh the snapshot.

pyreon info

The info command reports your environment and every installed @pyreon/* package version — and flags version skew before it bites.

pyreon info          # environment + installed @pyreon versions + skew check
pyreon info --json   # machine-readable report

It prints the CLI version, runtime (node / bun / platform), the project name (and whether it's a @pyreon/zero app), and the @pyreon/* packages in node_modules with their versions. Pyreon ships its packages on one synced version trajectory, so a healthy project has them all on the same version:

  ✓ All @pyreon packages on 0.37.0

When the installed set spans more than one version, info flags it — this is the condition that can trip the registerSingleton duplicate-instance guard ([Pyreon] Duplicate @pyreon/X detected) and split context/reactivity across instances at runtime:

  ! Version skew — 2 versions installed:
      0.37.0: @pyreon/core, @pyreon/router
      0.30.0: @pyreon/query

info is self-contained — it reads only your package.json and node_modules/@pyreon/*, so it works in any project (or none) without requiring the framework packages to be installed.

pyreon upgrade

The upgrade command is the fix for the skew pyreon info detects — it rewrites every @pyreon/* dependency range in package.json to a single version.

pyreon upgrade              # dry-run: print the alignment plan
pyreon upgrade --write      # apply (rewrite package.json), then install
pyreon upgrade --to 0.37.0  # target a specific version
pyreon upgrade --exact      # pin without the caret (0.37.0, not ^0.37.0)

By default the target is the highest @pyreon/* version already present (aligning laggards up); pass --to <version> to choose. It is dry-run by default — it prints what would change and applies nothing until you pass --write:

  pyreon upgrade → align 1 package(s) to 0.37.0

    @pyreon/core  ^0.30.0 → ^0.37.0

After --write, run your package manager's install to pull the aligned versions. workspace: / link: / file: / git specifiers and non-@pyreon dependencies are left untouched.

Upgrade codemods

When an upgrade crosses a release with a breaking change that can be migrated mechanically, pyreon upgrade also runs that release's codemod. It picks every codemod introduced after your lowest declared @pyreon/* version and at or before the target, runs them oldest first, and lists the files each would change. Like the dependency rewrite, it is a dry run until you pass --write; --json reports them under codemods.

  codemods for the breaking changes between 0.51.0 and 0.52.0:
    zero-remove-vite-option
      vite.config.ts

Codemods only make changes that keep behaviour the same, and skip node_modules, dist and lib. Current codemods:

IdSinceWhat it does
zero-remove-vite-option0.52.0Removes the vite option from zero({...}) in vite.config.*. zero() never read it; the old value is kept as a comment so you can move it into defineConfig.

Every @pyreon/zero changeset that bumps a minor version (a breaking change while Pyreon is 0.x) states how to upgrade in an Upgrade: line: none, the codemod id, or the manual step. That line appears in the changelog.

pyreon lint

pyreon lint is the unified front door to @pyreon/lint — it forwards every pyreon-lint flag verbatim.

pyreon lint                  # lint . with the recommended preset
pyreon lint src --fix        # auto-fix fixable findings
pyreon lint --preset strict  # any pyreon-lint flag works
pyreon lint --format json    # text (default) | json | compact
pyreon lint --watch          # re-lint on change
pyreon lint --lsp            # language-server mode (inlay hints + diagnostics)

pyreon lint and the standalone pyreon-lint binary share one implementation (the exported runCli entry), so the two can never drift. It exits non-zero when there are lint errors — drop it into CI exactly like pyreon-lint.

Programmatic API

@pyreon/cli exports its core functions so you can run the doctor or generate context from your own scripts (CI runners, custom dashboards, editor extensions):

import { doctor, generateContext } from '@pyreon/cli'
import type {
  DoctorOptions,
  DoctorReport,
  GateName,
  ContextOptions,
  ProjectContext,
} from '@pyreon/cli'

// Run the doctor and inspect the report
const exitCode = await doctor({
  cwd: process.cwd(),
  fix: false,
  json: false,
  ci: true,
  full: false,
  only: ['lint', 'react-patterns'] as GateName[],
})
// In --ci mode, the returned number is the count of non-advisory error
// findings (the CLI exits 1 when it's > 0). Without --ci, it's the total
// finding count and the CLI exits 0.

// Generate the project context programmatically
const context: ProjectContext = await generateContext({
  cwd: process.cwd(),
  outPath: '.pyreon/context.json',
})
console.log(context.components.length, 'components')

doctor(options) returns a Promise<number> (the exit-code-shaped count described above) and prints the chosen format to stdout. generateContext(options) returns the scanned ProjectContext and writes it to disk.

Reference

Commands

CommandDescription
pyreon check [paths] [--fix] [--json]Fast, file-scoped Pyreon/React anti-pattern scan (compiler detectors) with inline fixes. No paths → git-changed files. Exits non-zero on findings.
pyreon plain [paths] [--write] [--json] [--include-tests]Plain-Mode readiness report + classic→plain codemod. No paths → the whole tree under cwd.
pyreon add <pkg...> [--dry-run] [--json]Install @pyreon/* packages (PM auto-detected) and print a tailored setup recipe for each.
pyreon new [name] [--native]Scaffold a new Pyreon project (delegates to @pyreon/create-zero, or -multiplatform with --native).
pyreon mcp [args]Launch the Pyreon MCP server (delegates to @pyreon/mcp; prefers the project-local install).
pyreon atlas [args]Component workbench (delegates to @pyreon/atlas: init/scan/check/dev/verify/verify-browser/build).
pyreon loom [args]Dependency observatory (delegates to @pyreon/loom: scan/dev/build).
pyreon lathe [args]Spec-to-client generator (delegates to @pyreon/lathe: init/generate/check/diff/pull).
pyreon doctor [options]Project-wide health audit with a 0-100 score. Runs 13 fast gates by default; --full enables 2 slow gates.
pyreon context [--out <path>]Generate .pyreon/context.json for AI tools.
pyreon info [--json]Environment + installed @pyreon/* versions + version-skew check.
pyreon upgrade [--to <v>] [--write] [--exact] [--json]Align every @pyreon/* dependency to one version. Dry-run by default.
pyreon lint [paths] [--fix] [--watch] […]Run @pyreon/lint (forwards every pyreon-lint flag).
pyreon --help / -hShow usage.
pyreon --version / -vShow the CLI version.

doctor options

FlagTypeDefaultDescription
--fullbooleanoffInclude slow gates (audit-types, bundle-budgets).
--fixbooleanoffAuto-fix lint + react-patterns findings (writes to disk).
--only <gates>comma list—Run ONLY these gates.
--skip <gates>comma list—Skip these gates.
--format <fmt>text|json|ghatextOutput format.
--jsonbooleanoffShortcut for --format=json.
--ghabooleanoffShortcut for --format=gha.
--cibooleanoffExit non-zero on non-advisory error findings.
--audit-min-risk <r>high|medium|lowmediumMinimum risk for the audit-tests gate.
--roots <globs>comma listworkspace-discoveredOverride scan-root discovery for the file-scanning gates (relative to cwd).
--audit-testsbooleanoffLegacy → --only audit-tests.
--check-islandsbooleanoffLegacy → --only islands-audit.
--check-ssgbooleanoffLegacy → --only ssg-audit.
--check-contentbooleanoffLegacy → --only content-audit.
--check-nativebooleanoffLegacy → --only native-audit.

Gates at a glance

GateSpeedDefault categorySeverity range
react-patternsfastcorrectnesserror / warning / info
pyreon-patternsfastcorrectnesswarning
lintfastper-ruleerror / warning / info
distributionfastarchitectureerror
doc-claimsfastdocumentationerror
islands-auditfastarchitectureerror / warning
dependency-fabricfastarchitectureerror / warning / info
ssg-auditfastarchitectureerror / warning
content-auditfastarchitectureerror / warning
native-auditfastarchitecturewarning
audit-testsfasttestingerror / warning / info
check-dedupfastarchitectureerror
audit-leak-classesfastbest-practices (advisory)info
audit-typesslow (--full)architectureerror / warning / info
bundle-budgetsslow (--full)performanceerror / warning

Score formula

ConceptValue
Error penalty10 points
Warning penalty3 points
Info penalty1 point
Category subscore100 - clamp(sum of penalties, 0, 100)
Overall scoremean of included category subscores
Advisory categorybest-practices — scored + shown, excluded from grade & --ci
Grade A / B / C / D / F90+ / 80-89 / 70-79 / 60-69 / <60

Exports

ExportKindDescription
doctor(options)functionRun the health audit; returns the exit-code-shaped finding count.
generateContext(options)functionScan the project and write .pyreon/context.json.
DoctorOptionstypeOptions for doctor().
DoctorReporttypeThe aggregated report shape (--json output).
GateNametypeUnion of all 15 gate names.
ContextOptionstypeOptions for generateContext().
ProjectContexttypeThe scanned project shape.
@pyreon/cli