Stelint Runbook¶
Prerequisites¶
- Python 3.13 or later
- uv for dependency management
- A spaCy English model (
en_core_web_sm)
Install stelint¶
uv sync installs the en_core_web_sm model along with the dev dependencies, so
no separate download step is needed for local development or CI.
Install the spaCy model¶
Only needed when installing stelint from PyPI, where the model is not bundled:
Optional: sense2vec model¶
Stelint can use sense2vec for enhanced word sense disambiguation in Section 6 checks. This is optional — stelint works fully without it.
To install the English sense2vec model:
# 1. Download the model
wget https://github.com/explosion/sense2vec/releases/download/v1.0.0/s2v-reddit-2015-distinct.model.tar.gz
# 2. Extract to a directory
mkdir -p ~/.local/share/s2v
tar -xzf s2v-reddit-2015-distinct.model.tar.gz -C ~/.local/share/s2v
# 3. Set the environment variable
export STELINT_S2V_PATH=~/.local/share/s2v
Quick start¶
Lint a file:
Lint from stdin:
Usage¶
| Argument | Description |
|---|---|
FILE |
Path to a .md file to lint. Use - to read from stdin. |
--include-all |
Show all warnings, including those inside metadata regions such as headers, bold labels, tables, and code blocks. Without this flag, warnings inside metadata regions are suppressed. |
How it works¶
- The input text (Markdown) is preprocessed to strip non-prose elements such as code blocks, links, images, HTML blocks, table rows, and headers.
- The cleaned text is processed by a spaCy pipeline.
- All ASD-STE100 check functions run against the spaCy document.
- Issues are mapped back to their original positions in the input file.
- Issues inside metadata regions (headers, bold markers, links, etc.) are suppressed unless
--include-allis set. - Results are printed in Vale-compatible format:
Check categories¶
Stelint covers the following sections of the ASD-STE100 specification:
Section 1 — Words¶
- Approved words list
- Part of speech verification
- Approved meaning and forms
- Technical noun categories and approval
- Regional slang and jargon
- British English usage
Section 2 — Multi-word nouns¶
- Multi-word noun consistency
- Technical noun clarity
Section 3 — Verbs¶
- Verb forms and tenses
- Past participle as adjective
- Passive voice detection
-ingforms- Noun used as verb
Section 4 — Sentences¶
- Short sentences (max 35 words)
- Contractions
- Forbidden modals
- Vertical lists
- Connecting words
- Missing articles
- Article usage
Section 5 — Procedural writing¶
- Sentence length in procedures
- Multiple instructions per step
- Imperative mood in procedures
- Descriptive statement first
- Notes formatting
Section 6 — Descriptive writing¶
- Information structure
- Key words
- Paragraph topic and length
- Paragraph structure
Section 7 — Safety instructions¶
- Safety instruction format
- Safety instruction explanation
Section 8 — Punctuation and word count¶
- Semicolons, hyphens, parentheses
- Word count limits (with parentheses and numbers)
- Hyphenation patterns
- Vertical list colons
Section 9 — Writing practices¶
- Word usage and consistent terminology
- Phrasal verbs
- Consistent style
- Sentence construction variety
General Recommendations (GR-1 to GR-8)¶
- GR-1: Conjunction "that"
- GR-2: Ambiguous "with"
- GR-3: Ambiguous pronouns
- GR-4: Ambiguous "this"
- GR-5: False friends
- GR-6: Latin abbreviations
- GR-7: Gender pronouns
- GR-8: Possessive form
Examples¶
Lint a Markdown file¶
Pipe text from another tool¶
Include all warnings (even in headers and metadata)¶
Use as a Python library¶
import spacy
from stelint.stelint import main
# Load the model and run checks programmatically
nlp = spacy.load("en_core_web_sm")
# Import individual check functions
from stelint.checks_section1 import check_approved_words, check_part_of_speech
from stelint.checks_section4 import check_short_sentences, check_contractions
from stelint.checks_gr_recommendations import check_conjunction_that
doc = nlp("Your text here.")
issues = check_approved_words(doc)
Output format¶
By default, only prose issues are reported. Output follows the Vale format:
manual.md:12:5 STE100.ShortSentences: Sentence is too long (52 words). Max 35.
manual.md:28:1 STE100.PassiveVoice: Passive voice detected: "is carried out".
manual.md:45:10 STE100.MissingArticles: Missing article before "safety valve".
When --include-all is used, issues inside metadata regions are also shown with a region label:
Configuration¶
Stelint uses a layered glossary system. Constants are loaded in cardinality order — later layers override earlier ones.
Base layer (always loaded)¶
asd-ste100_base.jsonl is always loaded first with the lowest cardinality. It contains the full ASD-STE100 specification constants and cannot be removed or reordered via configuration.
User layer (optional)¶
Create glossaries.yaml anywhere and set STELINT_GLOSSARIES to its path (relative to CWD or absolute):
Example config at docs/examples/glossaries.yaml:
glossaries:
- path: ../../src/stelint/company_glossary.jsonl
cardinality: 100
- path: ./project_glossary.jsonl
cardinality: 200
Rules:
- path is relative to the directory containing glossaries.yaml.
- cardinality is an integer. Higher values override lower ones.
- asd-ste100_base.jsonl must not appear in this file. It is always loaded first with the lowest cardinality.
- If --glossaries is omitted, only the base layer is used.
JSONL format¶
Each line is a JSON object:
{"namespace": "words", "name": "NON_APPROVED_WORDS", "type": "mapping", "data": {"word": "replacement"}}
To remove a key from the base, use "__REMOVE__":
{"namespace": "words", "name": "NON_APPROVED_WORDS", "type": "mapping", "data": {"unwanted_word": "__REMOVE__"}}
Adding entries programmatically¶
from stelint.glossary import add_to_project_glossary
add_to_project_glossary(
namespace="words",
name="NON_APPROVED_WORDS",
key="approved_word",
value="allowed_term",
)
Suppressing specific checks¶
The preprocessor automatically suppresses certain checks inside specific regions:
| Region | Suppressed checks |
|---|---|
header |
MissingArticles, ConnectingWords |
bold_marker |
MissingArticles |
Use --include-all to disable all metadata-based suppression.
LLM-powered checks¶
Stelint can use a large language model to detect issues that spaCy cannot, such as words used with different meanings in different parts of the document (same-part-of-speech polysemy).
Enable LLM checks¶
Set the following environment variables:
| Variable | Description | Example |
|---|---|---|
STELINT_LLM_BASE_URL |
OpenAI-compatible API endpoint | http://llama:9999/v1 |
STELINT_LLM_MODEL |
Model name (default: local-ornith) |
local-ornith |
STELINT_LLM_API_KEY |
API key (any non-empty string for local models) | sk-no-key |
Example:
export STELINT_LLM_BASE_URL="http://llama:9999/v1"
export STELINT_LLM_MODEL="local-ornith"
export STELINT_LLM_API_KEY="sk-no-key"
uv run python -m stelint file.md
What the LLM checks¶
- LLMPolysemy: Detects when the same word is used with different meanings across the document, even when the part of speech is the same. spaCy can only detect polysemy at the POS level (e.g. "light" as NOUN vs ADJ), but the LLM can detect subtle meaning differences within the same POS (e.g. "run" meaning execute vs. manage, both as VERB).
Context classification¶
When LLM checks are enabled, stelint also classifies each sentence as PROCEDURAL, DESCRIPTIVE, or SAFETY, then suppresses context- inappropriate issues:
| Sentence type | Suppresses |
|---|---|
| PROCEDURAL | ImperativeInDescription, ParagraphStructure, ParagraphLength, ParagraphTopic |
| DESCRIPTIVE | NonImperativeInProcedures, SentenceLength |
| SAFETY | ForbiddenModals |
This prevents contradictory flags (e.g., imperative mood being flagged as wrong in both procedural and descriptive contexts).
Performance¶
- LLM checks are fully optional. Stelint works identically without them.
- Each LLM call has a 30-second timeout.
- LLM failures are non-fatal — stelint continues with spaCy results only.
- Results are cached per document to avoid redundant LLM calls.
- Words appearing fewer than 3 times are skipped to avoid noise.
- Rule-based pre-filters bypass the LLM for obvious cases, reducing latency.
Development workflow¶
| Task | Command |
|---|---|
| Sync dependencies | uv sync |
| Run stelint on a file | uv run python -m stelint file.md |
| Run tests | uv run pytest src/stelint/tests/ |
| Lint the code | uv run ruff check src/ |
| Build the package | uv run python -m build |
Configuration files¶
| File | Purpose |
|---|---|
pyproject.toml |
Python package metadata, build config, and dependency groups |
docs/examples/glossaries.yaml |
User glossary overrides (optional) |
src/stelint/asd-ste100_base.jsonl |
Base ASD-STE100 constants (always loaded) |
zensical.toml |
Documentation site configuration |
.pre-commit-config.yaml |
Pre-commit hooks for CI quality checks |
License¶
MIT