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/pyrightconfig, if any (pyrefly initcan 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
- Phase 1 — Observational mode (recommended starting point):
- Install Pyrefly and configure it in
pyproject.toml. - Add these rules to AI agent instructions.
- Add Pyrefly as a pre-commit hook, but do not enforce it as an absolute blocker during early development.
- Type every public function explicitly:
python
def f(x: InputType) -> OutputType:
- Forbid loose structures except at the raw input layer —
dict[str, Any]andlist[Any]are prohibited elsewhere. - Use a distinct type per pipeline phase and never reuse one type across phases:
python
RawNode
ParsedNode
ValidatedNode
SemanticElement
SheetSpec
RenderPlan
RendererConfig
- Keep identity types separate:
UUID,Name,Alias, andInternalIDmust not be used interchangeably — give each its own type or wrapper. - Never mix layers: do not pass raw AST to the renderer, do not use the
semantic model as a renderer config, do not mix
.modeland.view/.sheetconcepts, and do not treatElement,Diagram,Sheet, andProjectionas synonyms. - 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 isgenerate → check → fix → check → commit;generate → commit(skipping checks) is forbidden. - Phase 2 — Enforcement mode, once the codebase and types stabilise:
- Make Pyrefly a mandatory pre-commit check.
- Make Pyrefly a CI/PR merge gate.
- 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 checkruns cleanly (or only reports pre-approved legacy debt) on every modified file.- Public interfaces are explicitly typed;
dataclass/TypedDict/typed classes replace rawdictstructures 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/.sheetlayer (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
ruffis 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 usecast()without an architectural justification, and do not relax a type toobjectjust to silence the checker.
Task looks done but checks still fail
Cause:
ruffstill 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
generateandcheck— the workflow isgenerate → check → fix → check → commit.
Related Documents
- DocumentationStyleGuide.md - writing profile and document-type rules.
- GuideTemplate.md - guide document template.
- ManualsIndex.md - guide and manual index.
- Troubleshooting.md - fixing pre-commit and git errors, including
rufffailures. - ArchitectureOverview.md - technical architecture context.