-
Notifications
You must be signed in to change notification settings - Fork 0
Contributing
This page is orientation. It describes how the repository is laid out, how a change travels from idea to merged pull request, and which kind of contribution needs which kind of evidence. Read it to decide whether you can contribute, before you clone anything.
For exact commands, file formats and the review checklist, see CONTRIBUTING.md in the repository. This page does not repeat it.
Two properties of this project make it easier to work on than its subject matter suggests.
There is nothing to install. The linter is standard library only, on
purpose and permanently. Clone the repository and python -X utf8 ste_lint.py --help runs the tool. No virtualenv, no dependency resolution,
no build step. See Installation for the installed-package
route, which users want and contributors do not need.
The bulk of the rules are data, not code. What the linter catches lives in JSON tables of words and phrases, read off disk at startup. Adding a word the tool should flag means editing a JSON file and adding one example sentence to a test fixture. You never have to open the engine.
And the contribution this project values highest needs no code at all: a report of a rule firing on text that is correct.
src/ste100/ The package: engine, checks, rule data, presets
tests/ Eight test harnesses and their corpus fixtures
devtools/ Maintenance scripts, not shipped in the wheel
docs/ Reference documentation
examples/ Drop-in configuration for CI, pre-commit, VS Code
The whole tool. engine.py holds Engine, which composes the check modules
as mixins and runs them over each unit of text. The checks are grouped by
what they examine, not by rule number:
-
checks_lexical.py-- T1, T3 and T6. Word and phrase lookups against the loaded tables. -
checks_atomicity.py-- T5. Sentence structure: multipleshall,and/or, punctuation density, EARS template conformance. -
checks_reference.py-- T4. Pronouns and comparatives with no stated antecedent or baseline. -
csv_integrity.py-- theSTE-CSV-*checks, which examine relationships between rows instead of prose.
Supporting modules each do one job: paths.py loads config and rule data,
discovery.py walks the tree and detects which profile a file belongs to,
masking.py hides code spans so the checks never fire inside them,
units.py defines the Finding record, report.py formats output,
fixer.py implements --fix, explain.py implements --explain,
cli.py parses arguments, and helve.py is the JSON-RPC server.
Two subdirectories hold data instead of code:
-
src/ste100/data/*.json-- the bulk word and phrase tables. T1 substitutions, T2 vague terms, T3 hedges, T6 filler and AI tells, plus budgets and part-of-speech heuristics. These files are the source of truth and are edited by hand. -
src/ste100/presets/*.json-- the shipped presets.default.jsonis tuned for general technical documentation;veistra.jsonis the stricter original profile set. A preset defines the profiles, their path globs, severity tiers and thresholds. Configuration covers what each key does.
Fixed rule IDs outside the bulk tables -- T4, T5, the structural S7-*
checks, CSV integrity and budgets -- are declared as constants in
rule_ids.py, each beside the --explain text that describes it.
How the Linter Works walks through the pipeline in detail. This page only names the pieces.
Eight harnesses, each a plain Python script with no test framework behind
it. They cover the corpus, T1 suggestion collisions, --fix, --baseline,
configuration handling, the default preset's out-of-box quietness, the
JSON-RPC server, and adversarial CSV input. Each prints a distinct success
line, and CI checks for that line as well as the exit code, because one
harness once exited 0 after finding zero files to check.
Alongside them sit the corpus fixtures, which are how this project encodes what it believes:
-
corpus_clean/-- text that has to produce no findings at all. This is where a fixed false positive goes, permanently. -
corpus_dirty/-- text that has to produce specific findings, marked with inline`expect:RULE-ID`annotations. -
corpus_default/-- natural documentation the default preset has to stay quiet on. -
corpus_suggestions/-- text covering the replacements T1 rules hand out.
Maintenance scripts outside the installed package. collision_audit.py is
the one contributors touch: it checks that no T1 rule tells you to replace
a word with another word that a different rule bans. The rest are audit
reports carried over from the project this tool was extracted from.
One warning before you go looking: build_lint_data.py was the generator
that produced the JSON tables, and it cannot be run here. It reads a
source word list absent from this repository, and the tables have been
hand-edited since. Edit the JSON directly.
docs/ holds the reference material the wiki summarises: the full rule
list, configuration keys, integration recipes, and the HELVE server
protocol. examples/ holds files you can copy into your own project --
GitHub Actions and GitLab CI jobs, a pre-commit configuration, VS Code
tasks with a problem matcher. Integrations covers those
from the user's side.
Each needs different evidence, and knowing which one you have saves time.
The linter flagged text that was fine. This is the most useful report this project receives, and it needs no code.
Four fields make one usable: the exact sentence, the rule ID that fired, the profile the file was linted under, and the correct behaviour. The issue template asks for each. All four go into the regression test, which is what stops the same false positive from coming back.
Why this weighs heavier here than in other linters: a rule that catches genuine problems while also flagging correct usage is worse than no rule. It teaches people to skim past findings, and then the true ones go past too.
Three requirements, and they are firm:
- A citation. Every rule here names its source -- ASD-STE100 itself, INCOSE, the NASA Systems Engineering Handbook, MIL-STD-961E, the EARS templates, or Femmer et al. on requirements smells. See Simplified Technical English for what the underlying standard does and does not say.
-
A corpus fixture. An example in
corpus_dirty/that has to fire, and, where the word is plausibly ambiguous, a counterexample incorpus_clean/that has to stay silent. The counterexample is what gets a rule accepted. -
No collision. If a T1 rule's replacement word is banned elsewhere,
following the tool's advice creates a new finding.
devtools/collision_audit.pyfinds those, and the suggestion harness fails when it is not clean.
Adding a word to an existing table is routine. New check logic is a design conversation. Open an issue before writing it.
Extend whichever harness covers the surface you touched, and add no runtime dependency. The stdlib-only rule is not negotiable, and it is the review comment most likely to appear on a first pull request.
- Open an issue first for anything beyond a word-list addition or an obvious fix. For new check logic this is required, and it costs far less than discovering a design objection after you have written the code.
- Branch and change. Edit the JSON table or the module, add the fixture, and run the linter over your own example to watch the finding appear or disappear.
- Run all eight harnesses. They take seconds, and they are the same checks CI runs.
- Open the pull request and fill in the template. One logical change per pull request.
- CI runs three jobs: the harnesses across three operating systems and three Python versions, a packaging job that builds and installs the wheel, and a non-blocking job that lints this repository's own Markdown. That last one is advisory by design -- the shipped tables were calibrated for strict specification writing, and community-health prose legitimately uses words the stricter profiles reject.
- Review is by the maintainer. This is a one-maintainer project: expect days, not hours. A comment after a quiet week is fine.
Rule IDs are permanent. Every ID has the shape
STE-<test>-<CATEGORY>-<seq4>, for example STE-T1-SUB-0104, and they are
stable from v0.1.0 onward. People pin them in --baseline files and in
configuration overrides, so renumbering an existing entry breaks both
without any warning. Add new numbers; never reuse or renumber. A gap in the
sequence is fine.
Python 3.9 is the floor, and the trap is subtler than syntax. Parsing
the sources at that feature version catches new grammar, but not a standard
library method that gained a new keyword argument later. That shipped
once: Path.write_text(newline=...) is 3.10 and later, passed the syntax
check, and broke --fix on 3.9 on every platform until the matrix caught it.
Match the code that is there. No type annotations anywhere, .format()
instead of f-strings for messages, and comments that explain why the code is
shaped the way it is -- often by naming the bug that made it necessary.
The agent skill ships with the repository. If your change alters flags
or behaviour, check whether .claude/skills/ste100-lint/SKILL.md needs the
same update. See Agent Skill.
The project follows the Contributor Covenant. See CODE_OF_CONDUCT.md.
Report security issues privately through GitHub Security Advisories, never as a public issue; see SECURITY.md.
Contributions are licensed under Apache-2.0, matching the project. There is no separate agreement to sign.
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