SOTER

Pyrefly Type Checker Setup

Audience

AI agents and human developers who write or modify code in the SOTER / Logos pipeline (parser, AST, semantic model, .sheet/.view layer, renderer).

Goal

Install and use Pyrefly, alongside ruff and pytest, as the project's static type checker so that the strict layer separation of the Logos pipeline is enforced in code, not just in intent.

Pyrefly is a modern static type checker for Python, in the same class as mypy or Pyright but stricter:

Tool Character
mypy Classic, conservative, sometimes slow
Pyright Very fast, used in VS Code (Pylance)
Pyrefly Modern / experimental, more formally rigorous

Pyrefly does not design the Logos language. It enforces that the implementation does not betray the language's architecture. It acts as a semantic boundary guardian between pipeline layers, protection against conceptual drift during refactoring, a quality filter for AI-generated code, and support for safe metamodel refactoring.

It does not help decide whether the Logos ontology is well designed or whether a DSL rule is business-sensible — those are design and domain questions. It only checks whether the implementation is consistent with its own declared types and layer contracts.

Prerequisites

  • A Python project with pyproject.toml.
  • An existing mypy/pyright config, if any (pyrefly init can migrate from it).
  • Familiarity with the SOTER layer architecture that Pyrefly is meant to protect:

text .model → parser → AST → validation → semantic model → .sheet/.view → render plan → renderer.conf → output

  • Agreement to the mandatory typing rules below before writing code that crosses a pipeline boundary.

Workflow

  1. Phase 1 — Observational mode (recommended starting point):
  2. Install Pyrefly and configure it in pyproject.toml.
  3. Add these rules to AI agent instructions.
  4. Add Pyrefly as a pre-commit hook, but do not enforce it as an absolute blocker during early development.
  5. Type every public function explicitly:

python def f(x: InputType) -> OutputType:

  1. Forbid loose structures except at the raw input layer — dict[str, Any] and list[Any] are prohibited elsewhere.
  2. Use a distinct type per pipeline phase and never reuse one type across phases:

python RawNode ParsedNode ValidatedNode SemanticElement SheetSpec RenderPlan RendererConfig

  1. Keep identity types separate: UUID, Name, Alias, and InternalID must not be used interchangeably — give each its own type or wrapper.
  2. Never mix layers: do not pass raw AST to the renderer, do not use the semantic model as a renderer config, do not mix .model and .view/.sheet concepts, and do not treat Element, Diagram, Sheet, and Projection as synonyms.
  3. Run the quality pipeline after every change, in this order: ruff (style, simple errors, imports) → pytest (behaviour) → pyrefly (type contracts and layer boundaries). The cycle is generate → check → fix → check → commit; generate → commit (skipping checks) is forbidden.
  4. Phase 2 — Enforcement mode, once the codebase and types stabilise:
  5. Make Pyrefly a mandatory pre-commit check.
  6. Make Pyrefly a CI/PR merge gate.
  7. Bring the new-error count on modified code to zero.

Commands

Minimal installation:

pip install pyrefly
pyrefly init  # migrate from existing mypy/pyright config

Vibecoding cycle, run after every change:

ruff check .
pytest
pyrefly check

Pre-commit hook:

# .pre-commit-config.yaml
repos:
  - repo: https://github.com/facebook/pyrefly-pre-commit
    rev: <version>
    hooks:
      - id: pyrefly-check
        pass_filenames: false

Expected Result

  • pyrefly check runs cleanly (or only reports pre-approved legacy debt) on every modified file.
  • Public interfaces are explicitly typed; dataclass/TypedDict/typed classes replace raw dict structures outside the raw input layer.
  • Pipeline-phase types (RawNode, ParsedNode, ValidatedNode, SemanticElement, SheetSpec, RenderPlan, RendererConfig) stay distinct, and identity types are never interchanged.
  • Pyrefly is most valuable exactly at the seams: the parser/AST boundary (typed structures instead of loose dicts), the semantic validator (Raw AST → Normalised AST → Validated Semantic Model), the .view/.sheet layer (which AI will instinctively try to over-simplify), and the renderer bridge (where "what the model means" is easily confused with "how to draw it").
  • A task is considered complete only when ruff is clean, tests pass, and Pyrefly reports no errors in the modified code.

Troubleshooting

Pyrefly reports type errors

Cause:

  • An untyped or loosely typed structure (dict[str, Any], list[Any]) was used outside the raw input layer.
  • Two pipeline-phase concepts were mixed (for example, raw AST passed to the renderer, or the semantic model used as a renderer config).
  • Identity types (UUID, Name, Alias, InternalID) were used interchangeably.

Fix:

  • Fix the type model, the function contract, or the data flow — do not add Any, do not use cast() without an architectural justification, and do not relax a type to object just to silence the checker.

Task looks done but checks still fail

Cause:

  • ruff still reports errors, tests fail, or Pyrefly reports errors in the modified code.

Fix:

  • Treat the task as incomplete until all three checks pass; do not commit between generate and check — the workflow is generate → check → fix → check → commit.

Related Documents

SOTER v1.12.0-beta