Testing & Quality Gate
One command —
npm run check
— type-checks, lints, validates authored markup, and tests your
application code in a single pass, printing every problem as
path:line:col
and exiting non-zero on failure, so a human, CI, or an AI agent is told
exactly where to fix.
App-level convention
Caspian itself ships no test runner. This gate is adopted by the
application on top of the framework, so it validates your app code
(main.py,
src/**,
settings/*.py)
— never the framework under
.venv/
or node_modules/.
pyright
Type checking — the same engine Pylance runs in the editor, so IDE squiggles and the gate agree. Only errors fail the build; warnings and info do not.
ruff
Fast, correctness-focused linting. Reports dead code and common
mistakes, and can auto-fix the safe ones with
check:fix.
templates
Lints authored markup — .html
files and the triple-quoted markup inside single-file components —
for JSX patterns, non-existent directives, and non-raw
html(...) forms
that would otherwise blank a page with no console error.
pytest
Runs the suite under
tests/, streaming
one live line per test, and surfaces each failure by node id and reason.
The command
This is the single production gate. Run it after you create, edit, or delete app-owned Python — and get it fully green before treating the change as done.
Under the hood npm run check
calls uv run python settings/check.py.
When something fails, problems are grouped by file and printed as
path:line:col [tool:code] message.
Fix every reported location and re-run until clean.
Narrow to one tool while debugging
uv run python settings/check.py --only pyright # or ruff / templates / pytest
Auto-fixing lint issues
npm run check
only reports. To auto-fix the safe ruff findings it lists,
run:
npm run check:fix
That runs settings/fix.py,
which formats first (see
Formatting),
then applies the safe ruff fixes — dead imports, redundant code
— and then re-runs the full gate. Formatting leads so the fixes land
on final line positions rather than on lines the formatter is about to
move. Type errors (pyright) and failing tests (pytest) are
never auto-fixed — fix those by hand at the reported
path:line:col.
Formatting is not part of
npm run check
itself — the gate stays a read-only report. Run
npm run format
when you want files rewritten.
Why unused-import removal is guarded
Caspian single-file components import their children and use them only as
<x-*> tags
inside the template string (for example
from .Dialog import DialogContent
→ <x-dialog-content>).
Ruff can't parse the template, so it flags the import as unused
(F401) — but
casp resolves the tag from the module's globals at render time, so deleting
it would break the page.
Never stripped by ruff
F401 is marked
unfixable in
pyproject.toml,
so even a raw ruff check --fix
deletes no component import.
Only real dead imports fail
check:fix skips
any file that uses an import as an
<x-*> tag,
and the gate suppresses those matching reports. Shared detection lives in
settings/_component_imports.py.
Know the boundary
The templates tool
catches JSX-style markup and unknown directives, but the gate stops there.
It does not validate Tailwind / globals.css,
x-* tag
resolution, root-shape violations, or
public/js/**. An
unresolved x-*
tag or a PulsePoint runtime error passes the gate and surfaces only at
render time — so verify front-end changes by loading the affected
route in the browser.
You are not blind on that side, though. Caspian's dev stack forwards every
PulsePoint error to the npm run dev
terminal and appends it to
.casp/browser-log.jsonl,
so npm run logs
gives you a per-route verdict on what actually rendered — readable by
an AI agent that does not own the dev terminal.
npm run check
prints that digest at the end of its run, but never lets it change the exit
code. See
Browser Logs.
Configuration
Tooling and config live in
pyproject.toml.
Install or refresh the dev tools with:
uv sync --group dev
| Section | What it configures |
|---|---|
| [dependency-groups] dev | The pyright, ruff, and pytest dev dependencies. |
| [tool.pyright] |
Scope (include = ["main.py", "src", "settings/*.py"],
excludes .venv /
node_modules) and
typeCheckingMode = "basic".
|
| [tool.ruff] | Lint rules, with F401 kept unfixable. |
| [tool.pytest.ini_options] | Test discovery and default options. |