ste

ste finds high-confidence ASD-STE100 (Simplified Technical English) violations in Markdown and plain text.

It is one Go binary with 4 dependencies. It gives you the rule identifier, the exact character range, a message, a severity, and a confidence value for each finding. A person or a machine can then make the correction.

The rule numbers agree with ASD-STE100 Issue 9.

It is an aid for a writer. It is not an ASD-certified checker. It has 17 checks. The specification has 53 rules and a dictionary of approved words, and this tool does not contain that dictionary. Read the limits before you use it.

Installation

With mise

The GitHub backend of mise gets the binary from the releases of this repository:

mise use -g github:TudorAndrei/ste-cli        # the newest release
mise use -g github:TudorAndrei/ste-cli@0.9.0  # one version
ste version

To pin the tool for one project, put this in the mise.toml of that project:

[tools]
"github:TudorAndrei/ste-cli" = "latest"

mise finds the correct asset for your platform without an option. Each release also has a checksums.txt file. mise verifies the download against it and records the result in mise.lock. There are binaries for macOS (arm64, x64), Linux (arm64, x64), and Windows (x64).

With Go

go install github.com/TudorAndrei/ste-cli/cmd/ste@latest

From the source

git clone https://github.com/TudorAndrei/ste-cli
cd ste-cli
mise install
mise run build      # writes bin/ste

First steps

Give the tool a file, a directory, or standard input:

ste lint README.md
ste lint docs/
cat draft.md | ste lint -

A directory gives all .md, .markdown, and .txt files below it, but it does not give:

The tool always reads a file or a directory that you give by its path. --all removes both filters.

The text output has one line for each finding, and then a summary:

draft.md:3:15: warning [STE-8.1] The semicolon is the one punctuation mark that ASD-STE100 does not approve.
    Write two sentences, or use a list.
draft.md:3:48: warning [STE-4.2] The contraction "isn't" is not approved.
    Write "is not".

2 findings in 42 words of 1 file (4.76 for each 100 words)

The last number is the score: the number of findings for each 100 words.

Modes

Mode Findings Severity
flavored (default) Confidence of 0.60 or more as given by the rule
strict all one step stronger
ste lint --mode strict procedures/

Use flavored for a README or a design document. Use strict for a procedure, where a wrong instruction has a cost.

The mode does not change the sentence limit. ASD-STE100 selects the limit from the type of the sentence:

Sentence Limit Rule
An instruction in a procedure (a numbered list item) 20 words 5.1
A note (a line that starts with “NOTE:”) 25 words 5.5
Descriptive text 25 words 6.3

--max-words replaces both limits.

How to adopt it

A checker that reports 1000 findings on its first day is a checker that a team removes. This tool is an aid, and not a gate:

The sequence for a repository that has documentation already:

ste lint .                    # see the size of the problem
ste baseline .                # accept it, and write .ste-baseline.json
ste lint --fail-on-new .      # from now, only a new violation fails

The number in the baseline goes down when you correct the text. Write the file again with ste baseline . to record the new, lower number.

Silence one finding in the text

<!-- ste-disable-next-line -->
This sentence is not checked.

The tool does not check this line. <!-- ste-disable-line STE-3.6 -->

<!-- ste-disable STE-8.1 -->
The tool does not check this block.
<!-- ste-enable STE-8.1 -->

A directive with no rule identifier applies to every rule. A directive with no ste-enable applies to the end of the file.

The three gates

Gate Blocks when
--fail-on-new A finding is not in the baseline
--warnings-as-errors A finding has the warning severity, which becomes an error. An info finding stays advice.
--fail-over <n> The score for each 100 words is more than n

A gate reads the report after the baseline. An accepted finding never blocks. You can give more than one gate.

The dictionary

ASD-STE100 has two parts. This tool has the writing rules of part 1. Part 2 is the dictionary: about 900 approved words, and about 1300 words that the standard does not approve, each with its approved alternative. Rule 1.1 needs that dictionary.

This tool does not ship the dictionary. The specification is the property of ASD, and its terms permit no reproduction or publication without written authority. But ASD gives the specification free of charge to each writer and user at asd-ste100.org. You make the index from your own copy:

# 1. Get your own copy from asd-ste100.org, then make the text:
npx -y @firecrawl/anydoc ASD-STE100_ISSUE9.pdf -o ste100.md

# 2. Make the index. It goes in your cache directory, not in the project.
ste dict import ste100.md

# 3. Rule STE-1.1 uses it only when you ask:
ste lint --use-dict docs/

dictionary: true in the config does the same as --use-dict. ste dict info shows the index, and ste dict remove deletes it.

One import for each machine

The index is global. Import it one time, and each project on that machine reads the same file. No lint run reads the specification again: the run reads a JSON index of about 135 kB. The dictionary adds no measurable time to a run.

The index is data, and not a cache, because this tool cannot make it again without your copy. Thus it goes in the data directory of the XDG specification:

Path Condition
$STE_DICT an explicit path
$XDG_DATA_HOME/ste/dictionary.json you give that variable
~/.local/share/ste/dictionary.json Linux and BSD
~/Library/Application Support/ste/dictionary.json macOS

ste dict path prints the path that applies.

The dictionary is off by default, and this is why

ASD-STE100 approves about 900 words, for the maintenance of an aircraft. Ordinary software documentation uses many words that this dictionary does not approve: “state”, “file”, “build”, “should”. On a repository of 180 files, the rules of part 1 gave 1037 findings, and the same repository with the dictionary gave 9490.

The dictionary is correct. It is also too much for a README. Use it for a procedure, and use the baseline and min_confidence for the rest.

The technical nouns of your field

ASD-STE100 is a language for the maintenance of an aircraft. Its dictionary does not approve “hook”, “file”, “build”, or “graph”, because those words are not part of that field. Rule 1.5 of the standard gives 22 categories of technical noun, and category 19 is computer science and information technology. Thus a writer of software documentation is permitted to use the technical nouns of software.

presets: [software]     # or: ste lint --preset software

The preset holds about 170 technical nouns of software and of information technology. On one repository of 180 files, the dictionary gave 8213 findings, and the dictionary with this preset gave 6709.

What the tool cannot know

The dictionary gives a part of speech for each word, and this tool has no part-of-speech tagger. “graph” is an example: the dictionary does not approve the verb “graph”, but “a graph” is a correct technical noun.

The tool uses the shape of the sentence in place of a tagger: a determiner in the same noun phrase makes the word a noun. Thus “The dependency graph is large” gives no finding, and “Graph the test results” gives one. A modal A modal verb ends the phrase, and “The tool can graph the results” gives a finding.

The test is not perfect. A word that the dictionary has only as a verb gets a confidence of 0.60. The other words get 0.95. Thus min_confidence: 0.7 removes the first class.

The glossary is the correct answer for your technical nouns. Rule 1.6 and rule 1.8 of the standard tell you to do the same:

allow:
  nouns: [pump, valve, actuator]

Config

The tool reads the first of .ste.yml, .ste.yaml, glossary.yml, or docs/glossary.yml. Every key is optional.

mode: flavored          # or strict

rules:                  # off, info, warning, or error
  STE-8.1: error        # your text already obeys this rule
  STE-3.6: info         # this rule needs a rewrite. Advice for today.
  STE-5.1: off          # this rule comes later

exclude:                # the tool also skips the files that git ignores
  - "**/fixtures/**"
  - "*.generated.md"

allow:                  # the technical words of your project
  nouns: [parser, webhook]
  verbs: [provision]

prefer:                 # rule STE-1.11: one name for each item
  "config file": ["settings file", "configuration file"]

min_confidence: 0.6     # remove each finding below this value
max_words: 25           # replace the sentence limits of the standard
baseline: .ste-baseline.json
fail_over: 2.5          # without this key, the tool never blocks
warnings_as_errors: false
Key Function
mode flavored or strict
rules The severity of one rule: off, info, warning, or error
exclude The path patterns that the tool does not read
allow.nouns The technical nouns of the project
allow.verbs The technical verbs of the project
min_confidence Remove each finding below this value
max_words Replace the sentence limits of the standard
baseline The path of the file of accepted findings
fail_over The score that makes the command exit with code 1
warnings_as_errors Make each warning an error, and exit with code 1
dictionary Use the imported ASD-STE100 dictionary for rule STE-1.1
presets The subject fields whose technical nouns are permitted
analyzer Start the analyzer of the grammar for each run

An unknown key is an error. A spelling mistake in the file never stays hidden. --config <path> reads a different file, and --no-config reads no file.

Use in CI

The JSON output gives the rule identifier, the message, the severity, the confidence, the byte offsets, the line, the column, and the suggestion:

ste lint --format json docs/
{
  "version": 1,
  "mode": "flavored",
  "files": [
    {
      "path": "docs/draft.md",
      "words": 42,
      "findings": [
        {
          "rule_id": "STE-8.1",
          "message": "The semicolon is the one punctuation mark that ASD-STE100 does not approve.",
          "severity": "warning",
          "confidence": 1,
          "start": 23,
          "end": 24,
          "suggestion": "Write two sentences, or use a list.",
          "file": "docs/draft.md",
          "line": 3,
          "column": 15,
          "text": ";"
        }
      ]
    }
  ],
  "summary": { "files": 1, "words": 42, "findings": 1, "score": 2.38 }
}

--fail-over gives a non-zero exit code when the score is too high. A gate on the score, and not on the count, permits a long document.

ste lint --fail-over 2.5 docs/
Exit code Condition
0 The tool ran. Findings do not change this code.
1 You gave a gate (--fail-on-new or --fail-over) and the text does not pass it.
2 A flag, a file, or the glossary has an error.

A GitHub Actions step:

- uses: jdx/mise-action@v4
- run: mise use -g github:TudorAndrei/ste-cli
- run: ste lint --fail-on-new docs/

--fail-on-new is the correct gate for a repository that has documentation already. The findings in the baseline do not stop the work. A new violation does.

What the tool does not examine

The tool replaces these spans with spaces before it applies the rules. Your code examples give no findings:

A heading, an empty line, and the start of a list item are sentence boundaries. A list item keeps the lines that continue it. Each cell of a table row is a different sentence.

The tool counts a word with the rules of section 8. A hyphenated word, a quantity with its unit, a quoted string, and text in parentheses each count as one word.

The rules

Rule Name Example that it reports
STE-1.1 Unapproved word or word group “Utilize the tool in order to start”
STE-1.14 British spelling “colour”, “centre”
STE-3.4 Complex verb construction “has been sent”
STE-3.5 Progressive “-ing” form “is still running”
STE-3.6 Passive voice “was approved by the manager”
STE-3.7 A noun for an action “do a check of”
STE-4.2 Contraction “isn’t”
STE-4.3 A list with two constructions one item of three starts differently
STE-5.5 An instruction in a note “NOTE: You must…”
STE-6.6 Paragraph too long a paragraph of 7 sentences
STE-7.3 A safety instruction with no explanation a warning of one sentence
STE-5.1 Sentence too long a 26-word sentence
STE-8.1 Semicolon “Open the valve; then start the pump”
STE-9.3 Phrasal verb “carry out the test”
STE-GR-6 Latin abbreviation “e.g.”

A check with the GR prefix is a general recommendation of the standard. A general recommendation is advice and not a rule. Thus this tool gives it the info severity.

rules.md gives each rule, its confidence values, and its known limits.

More exact rules with an analyzer

Some rules need the grammar of a sentence. “The demand is measured” and “there is measured demand” have the same words, and only the grammar gives the difference.

Go has no library that gives the grammar of English with a trained model. An external program gives it:

ste analyzer                 # what it needs, and how to install it
ste lint --analyze docs/     # use it

The command sends only the sentences of a possible finding, and it keeps each answer. On a repository of 180 files, the analyzer removed 26 wrong findings of rule 3.6, and the run went from 0.24s to 1.30s.

The analyzer is never necessary. Each rule also works without it, and the command stays one binary with no runtime.

For an agent

An agent is a first-class reader of this tool. ste schema prints the full interface as JSON. It gives each command and each flag, with the permitted values. It also gives each config key, each rule with its number in the standard, the fields of a finding, and the exit codes.

ste schema                                     # what this tool accepts
ste lint --format json --summary docs/         # 226 bytes, not 4 MB
ste lint --format json --limit 20 --fields rule_id,file,line,text docs/
ste lint --format ndjson docs/                 # one object for each line
ste baseline --dry-run --format json .         # the plan, and no change

AGENTS.md gives the rules for an agent. It tells how to keep the output small, why exit code 0 is not proof of a clean document, and which findings need a person.

Commands

ste lint [flags] [path ...]   Check files, directories, or standard input
ste eval [flags] <dir>        Measure the rules against a labeled corpus
ste version                   Print the version
ste help                      Print the usage
Lint flag Function
--mode flavored (default) or strict
--format text (default) or json
--fail-over Exit with code 1 when the score is more than this value
--max-words Replace the sentence limit of both sentence types
--config Path of the glossary file
--no-config Do not read a config file
--baseline Path of the file of accepted findings
--fail-on-new Exit with code 1 when a finding is not in the baseline
--warnings-as-errors Make each warning an error, and exit with code 1
--use-dict Use the imported ASD-STE100 dictionary for rule STE-1.1
--preset Add the technical nouns of a subject field: software
--analyze Use the analyzer for the rules that need the grammar of a sentence
--analyzer Command of a different analyzer
--dict Path of the dictionary index
--all Read every file, and not only the files that git shows

A flag can come before or after a path.

Measure the rules

ste eval measures the precision and the recall for each rule against a labeled corpus. A fixture file can have a <name>.expected.json file with the findings that the tool must give. A file with no expectation file must give no findings.

ste eval testdata
rule          tp    fp    fn  precision   recall
STE-1.1        2     0     0       1.00     1.00
STE-3.6        1     0     0       1.00     1.00
all           20     0     0       1.00     1.00

evaluation.md gives the measurements and says why the number on a self-written corpus is weak.

What the tool does not check

ASD-STE100 has 53 rules. This tool checks 15 of them today, and about 31 can have a mechanical answer. The other rules need a reader: “Make sure that each paragraph has only one topic” is an example.

coverage.md gives each rule and its group. The ste-review skill gives the rules of judgment to an agent, which reports them as advice, and not as a defect.

Limits

Inspirations

This project started with an audit of the tools that came before it. Each one gave an idea. No code and no data from these projects is in this repository. upstream-audit.md records the license, the commit, and the decision for each one.

Project What it gave to this project
stazelabs/ste The nearest reference: a Go CLI with no dependencies, byte spans, JSON output, and the score for each 100 words. Its README is also one of the text samples that found false positives in these rules.
valeratrades/ste_checker The glossary workflow, and a labeled test corpus with a truth file.
sourdough-bread/asd-ste100-checker The structured result format with a rule identifier for each finding, and the MCP and LSP interfaces for a later version.
swarooppatilx/ste100 One test file for each rule, and annotated sample documents.
openste/openste An open word list with a part of speech for each word. This project does not use the data, because the provenance is not clear.
TechScribe term checker The key idea that a technical noun and a technical verb need different project configuration. allow.nouns and allow.verbs come from this.

License and the specification

Two different licenses apply, and they cover different things.

The code of this tool is under the MIT license. You can use it, change it, and put it in a product. The file is LICENSE.

The specification is not ours, and the MIT license does not touch it. ASD-STE100 is the property of the AeroSpace and Defence Industries Association of Europe. Its copyright notice does not permit reproduction in whole or in part without written authority. This tool holds no text of the specification, and it holds no part of the dictionary. It holds rule numbers, numeric limits, and our own words.

To get the specification, go to asd-ste100.org, where ASD gives it free of charge to each writer and user. This tool does not give you the specification, and it does not give you certification.