-
Notifications
You must be signed in to change notification settings - Fork 0
Configuration
The linter needs no configuration to run. This page is the task-oriented
tour: how config resolves, what the shipped presets and profiles are for,
how severity is decided, and how to adopt the linter on an existing
codebase. For the exhaustive key-by-key reference, see
docs/configuration.md
in the repository.
First match wins, verified against src/ste100/paths.py:resolve_config():
-
--config path/to/file.json-- an explicit file wins outright. -
--preset NAME-- a preset shipped inside the package (src/ste100/presets/). -
A project-local
ste100.jsonor.ste100.json, found by walking up from the target directory (or--root, if given) toward the filesystem root. Drop one of these next to your docs and plainste100picks it up with no flags. -
The shipped
defaultpreset, if nothing above matched.
ste100 --preset default docs/ # explicit preset
ste100 --config ./my-ste100.json docs/ # explicit file, highest priority
ste100 docs/ # falls back through 3, then 4Verified: running ste100 with no --config/--preset from a subdirectory
that has an ste100.json two levels up in its ancestry picked up that file
without any flag.
-
default-- the generic preset for any project. Use this one first. -
veistra-- a byte-for-byte copy of the config this linter was extracted from (Firelight's internal document-control monorepo). Its profiles (core,csv,spec,design,vision,prose) and itscsv_integritychecks match that project's specific registry schema (truths.csv,decisions-*.csv,terminology.csvwith columns such assuperseded_byandreview_by). Use it if you are that project, or as a complete worked example with every feature turned on, including CSV integrity checking.
ste100 --preset default docs/
ste100 --preset veistra docs/Every linted file is assigned a profile, which controls which of the
six tests run against it and what its word/ARI budgets are. A profile is an
editorial policy, not a property of a file extension. The same .md file
resolves to a stricter or looser profile depending on where it lives or an
override.
Verified against src/ste100/discovery.py:detect_profile(), in this exact
order:
-
--profile NAMEon the command line -- overrides everything, for every targeted file in the run. -
A first-line comment,
<!-- lint-profile: NAME -->, honored only ifNAMEnames a profile that exists in the resolved config. An unknown name is ignored, and detection falls through to the next step. -
profile_orderglob matching. The config'sprofile_orderlist is walked in order; for each profile, each of itspath_globsentries is tested against the file's path (measured from the resolution root, with/separators) using Python'sfnmatch.fnmatch(). First match wins. -
prose, unconditionally, if nothing above matched.
<!-- lint-profile: spec -->Five profiles exist in default's profile_order:
spec, reference, csv, docs, prose.
-
prose-- the unconditional fallback for anything that matches nothing else. T1/T3/T6 (replaceable words, optionality, zero-information) stay aterror; every other test is capped toreviewby a profile-wide cap (see Severity resolution below). This is the safest profile to run against a whole existing repo the first time -- it does not fail CI on prose-quality nitpicks, only on the three checks with the lowest false-positive rate. -
docs(README.md,CONTRIBUTING.md,SECURITY.md,CODE_OF_CONDUCT.md,CHANGELOG.md,docs/**,examples/**) -- ordinary project documentation. This profile is newer than the rest (its own note indefault.jsondates it to this project's 2026-08 out-of-box-experience retune) and is not yet described indocs/configuration.md. This section is the current source of truth fordocs, not that file. It exists because dogfooding the linter on this project's own README and CONTRIBUTING guide found theproseprofile's non-T1/T3/T6 checks firing on unremarkable English (should,may,e.g.), not on genuine defects -- T1 stays aterror(its suggestions are still good advice on ordinary prose), and a long list of individualseverity_overridesentries pin the rest toreview, reproducing theprosecap's behavior by hand under a profile name the cap does not special-case. -
spec(*spec*/**,*requirements*/**,SPEC.md,REQUIREMENTS.md, ...) -- formal requirements and specifications: shall-statements, EARS templates, atomicity enforcement. The name is conventional, not load-bearing: the EARS, indefinite-article, and zero-shallchecks fire for whichever profiles listears(orears_review) intests. A profile namedrequirementsgets them under a custom config, the same asspecdoes. -
reference(*reference*/**,*api*/**,REFERENCE.md,API.md, ...) -- reference/API documentation: parameter tables, enumerations, code samples. Runs the same test set asspecminus EARS. -
csv(*.csv) -- generic CSV cell linting: T1/T3/T6 word-level checks plus per-field word budgets, run against every cell of every row.csv_integrityis left out of this profile'stestson purpose (see below).
csv_integrity (STE-CSV-0001..STE-CSV-0010) validates a bespoke
registry schema -- specific filenames and columns from Firelight's own
document-control process; other projects do not have them. Running it
against an arbitrary CSV either finds nothing, or produces a confusing
false positive on an unrelated CSV that shares a column name such as
review_by. Neither is useful to a new user.
Worth knowing, verified by running the tool: today, turning csv_integrity
off in a profile's tests list is cosmetic, not an enforced gate. main()
in src/ste100/cli.py calls check_csv_integrity() unconditionally for
every discovered .csv file, without consulting any profile's tests
list. A dirty registry-shaped CSV fixture still produced STE-CSV-*
findings against default's csv profile, whose tests list excludes
csv_integrity.
Three tiers -- error (shown, fails the run), warning (shown, does not
fail the run), review (hidden unless --stats, never fails the run).
Severity for a given finding is resolved once, by
Engine.severity() in src/ste100/engine.py. Verified against that code
and against docs/rules.md, the precedence, in this exact order:
-
The
proseprofile-wide review-cap, applied first, unconditionally. If the file's profile isproseand the check is not T1, T3, or T6, the finding is forced toreviewbefore any rule-specific override is even consulted. This cap is hardcoded to the profile nameprose-- the newerdocsprofile has a different name and reproduces the same behavior through explicitseverity_overridesentries instead (see above), not through this cap. -
A rule-specific
severity_overridesentry for the exact profile. The first entry whoserulematches and whoseprofileequals (or lists) the current profile wins outright, regardless of where it sits in the list compared to a wildcard entry. -
A
severity_overridesentry withprofile: "*"(applies to every profile) -- but a later specific-profile match for the same rule anywhere in the list still wins; a wildcard match only sets a tentative answer and keeps scanning. -
severity_defaults, keyed by rule name. -
The hardcoded literal passed as a default at the call site in
src/ste100/checks_*.py, used only when none of the four rules above apply.
One point worth carrying over from docs/rules.md: a "*" override beats
severity_defaults in every case. Retuning a rule's severity_defaults
entry that already has a wildcard override has no visible behavior change;
change the override instead.
Running a new linter over years of existing documentation produces an unusable wall of findings on day one. A baseline snapshot lets you record what exists today and enforce only on what changes afterward.
ste100 --format json docs/ > .ste100-baseline.json # snapshot today's findings
ste100 --baseline .ste100-baseline.json docs/ # only new findings surfaceTwo semantics matter here, both verified by execution:
-
Suppression is by occurrence count, not by mere presence. The
baseline is keyed on
(file, rule, message), and each key is allowed that number of matches per run. Matching on presence alone means a file with one baselinedutilizeswallows everyutilizeadded to it later, hiding new regressions -- counting occurrences means a new instance of an already-baselined problem still surfaces. Verified: a file baselined with two findings on one sentence stayed clean after baselining, but adding a second sentence with the same two rule violations produced two new findings and a nonzero exit code. - Line numbers are excluded from the match key, on purpose. They shift every time anyone edits above a finding, and a baseline that invalidates itself on every unrelated edit is worse than no baseline at all.
The following config keys are parsed but do not drive any behavior yet, verified by grepping the source for each key name, not by inspection of the schema alone:
-
thresholds(smell_density_max,passive_ratio_max,paragraph_sentences_max) -- the run summary reportssmell_densityandpassive_ratioas informational metrics, but nothing compares them against these configured thresholds -- no pass/fail gate ties to them.paragraph_sentences_maxis doubly dead: the enforced paragraph budget comes fromsrc/ste100/data/budgets.jsoninstead, a separate value this key does not drive. -
Per-profile
ari_target--ari_grade()computes one whole-corpus score for the run summary; no per-profile comparison against a target exists anywhere in the codebase. The key is parsed into the config dict and never read again. -
severity_defaultsis read (see Severity resolution above) -- not part of this dead-keys list. Do not confuse it withthresholdsorari_target.
Also unread: profile_override_comment (the regex is hardcoded in
discovery.py), budgets_file (budgets load from
src/ste100/data/budgets.json directly), rule_id_taxonomy (purely
descriptive), t5_oblique_slash_exceptions_regex and s7_tbd_pattern
(each has a hardcoded regex instead), and top-level schema_version
(report.py uses its own fixed constant). These are kept in the shipped
presets to keep the schema stable, and to give a future implementation sane
defaults to inherit instead of a missing key.
Copy src/ste100/presets/default.json as a base -- the code requires the
following keys at load time (it raises on load if any is missing):
t4_pronouns, t4_comparative_irregulars, t4_comparative_exclusions,
t4_comparative_min_stem_length, t5_combinators,
t5_punctuation_density_max, t5_punctuation_chars, s7_units,
universal_quantifiers, nasa_arm_directives, abbreviation_allowlist,
profiles, profile_order, never_lint. Verify a new config loads and
produces sane output before trusting it:
ste100 --config your_config.json --stats path/to/one/file.md--stats is worth keeping on while developing a config -- it surfaces
review-tier findings that are otherwise silent, including any place a
profile-wide cap demotes a check you expected at a higher tier.
For the complete key-by-key table -- every config key, whether the code
reads it, its meaning, and its default -- see
docs/configuration.md
in the repository. That page also covers fnmatch glob gotchas (*
matches /, **/ requires a literal separator before it, case sensitivity
depends on the host OS) and how never_lint path-segment matching works,
in more depth than this page repeats.
-
Installation -- getting
ste100running. -
CLI Reference -- every flag, including
--config,--preset,--profile, and--baseline. - Simplified Technical English -- what the six tests check and why.
- How the Linter Works -- the engine internals behind severity and profile resolution.
- Agent Skill -- driving the linter from an AI coding agent.
- Contributing -- adding a rule or a profile.
STE-Linter — Apache-2.0, Firelight Innovations. Not a licensed or certified implementation of ASD-STE100. See Simplified Technical English.
Start here
The standard
Using it
Contributing