Prose analysis
MarkSpec performs quality analysis on the body prose of authored entries — the
paragraphs and list items that describe what a requirement means, not just its
identity. Prose analysis runs as a separate markspec lint pass after parsing
and validation; it does not block markspec validate.
Scope
Only authored entries of Specification-family types are analysed. The scope predicate is:
shape = Authored AND core type ∈ { Requirement, Test, Contract, Record, Risk }
or a profile subtype of any of the above
Reference-shape entries are excluded — they point to external documents whose
prose MarkSpec does not own. Core types outside the Specification family (e.g.
SoftwareComponent, Definition, Annotation) are also excluded; structural
descriptions and glossary entries follow different writing conventions.
Suppression-hygiene rules (MSL-Q9xx) run on all authored entries
regardless of type.
Modal keyword analysis
Modal keywords signal the obligation level of a requirement. MarkSpec enforces the RFC 2119 / EARS convention that modals appear in lowercase:
| Keyword | Obligation |
|---|---|
shall | Mandatory |
shall not | Prohibited |
should | Recommended |
should not | Not recommended |
may | Optional |
must | External constraint (use shall for internal) |
must not | Prohibited (external constraint) |
MSL-M060 — modal-keyword-uppercase (warning)
Fires when any of the above keywords appears in uppercase (SHALL, MUST, …)
inside body prose. The formatter (markspec format) rewrites uppercase modals
to lowercase automatically, so this diagnostic appears only on files that have
not been formatted.
warning[MSL-M060]: requirements.md:12 modal keyword 'SHALL' in body prose is
uppercase (spec §3.4.1 canonical form is lowercase; 'markspec format' will
rewrite it)
Verbatim blocks (fenced code, math, feature snippets) are excluded — modal keywords inside code examples are not checked.
MSL-M061 — missing-modal-keyword (info)
Fires on Requirement entries (and profile subtypes) whose body contains no
modal keyword at all. This is a style hint: a requirement without an obligation
word is often a description masquerading as a requirement.
info[MSL-M061]: requirements.md:7 Requirement entry contains no modal keyword
(shall / should / may / must) — consider declaring one to make the obligation
explicit
EARS pattern recognition
EARS (Easy Approach to Requirements Syntax) defines five body forms for requirement entries. MarkSpec recognises the leading keyword of each form and uses it internally for normalization — capitalisation is preserved at sentence start and lowercased mid-sentence.
| Form | Leading keyword | Template |
|---|---|---|
| Ubiquitous | (none) | The system shall … |
| State-driven | While | While state, system shall … |
| Event-driven | When | When event, system shall … |
| Unwanted | If | If condition, system shall … |
| Optional | Where | Where feature, system shall … |
The EARS form is currently used for normalization only; no lint rule fires on an
EARS keyword choice. Future rule group ears is reserved for form-specific
checks (e.g. ensuring state-driven requirements name a concrete state).
GWT pattern
GWT (Given / When / Then) is the standard body form for Test entries. MarkSpec
accepts GWT prose without enforcing structural rules in the current phase; the
three clauses are plain prose paragraphs.
Recommended form:
- [SWT_BRK_0030] Debounce rejects short pulses
Given the debounce threshold is 10 ms, When a pulse of 5 ms arrives, Then the
output shall remain unchanged.
Id: 01HGW3R9QNP4ABCDEFGHJKMNPQ
Type: test
Verifies: 01HGW2Q8MNP3RSTVWXYZABCDEF
GWT-specific lint rules (detecting missing clauses, mixed-form bodies) are planned for a future phase.
INCOSE lexicon rules
These rules flag vocabulary patterns that the INCOSE Guide to Writing Requirements (GtWR) identifies as common sources of ambiguity. All rules apply to prose-bearing blocks (paragraphs, notes, list items); tables, code, and math blocks are excluded.
| Code | Name | Severity | Examples of flagged text |
|---|---|---|---|
| MSL-Q302 | incose-r7-vague-term | warning | some, several, many, adequate, sufficient, reasonable, as needed |
| MSL-Q303 | incose-r8-escape-clause | warning | as appropriate, where possible, if practicable, to the extent possible |
| MSL-Q304 | incose-r9-open-ended | info | including but not limited to, etc., and/or |
| MSL-Q305 | incose-r10-superfluous-infinitive | info | be able to, be designed to, in order to |
| MSL-Q310 | incose-r26-absolute | info | 100%, always, never, complete, entirely |
| MSL-Q313 | incose-r16-not | info | not (whole-word; excludes note, notation) |
Rules MSL-Q302 and MSL-Q303 are warning severity — they flag text that
routinely causes requirement verification ambiguity. The remaining rules are
info — informational hints that the author should consider but that do not
necessarily indicate a defect.
Example
warning[MSL-Q302]: src/braking/requirements.md:14 vague term 'sufficient' in
body prose — specify a measurable threshold instead (INCOSE GtWR R7)
warning[MSL-Q303]: src/braking/requirements.md:19 escape clause 'as appropriate'
weakens verifiability (INCOSE GtWR R8)
Structural quality rules
Two rules check the shape of entries rather than their vocabulary.
MSL-Q400 — struct-title-length (info)
The entry title should be between 3 and 120 characters. A 1–2 character title is almost certainly incomplete; a title longer than 120 characters is usually a sentence that belongs in the body.
MSL-Q401 — struct-body-length (info)
The entry body should contain between 5 and 500 words. An entry with fewer than 5 words has no meaningful description; one exceeding 500 words is likely describing multiple concerns that should be split.
Both thresholds are hard-coded defaults in the current phase. Profile-level
configuration (e.g. prose.struct.title.maxLength) is planned but not yet
implemented.
Suppression
A rule can be silenced for a specific entry by adding two trailer attributes:
- [SRS_BRK_0108] Legacy inherited requirement
The system shall operate as appropriate for the ambient conditions.
Id: 01HGW2Q8MNP3RSTVWXYZABCDEF
Markspec-disable: MSL-Q303
Rationale: Verbatim from customer SRS version 1.2; cannot be rephrased
without an approved change request.
Both Markspec-disable and Rationale must be present; a suppression without a
rationale fires MSL-Q900 (disable-without-rationale). Citing an unknown rule
code fires MSL-Q901 (disable-unknown-rule). These two hygiene rules run on
all authored entries regardless of type and cannot themselves be suppressed.
Running prose analysis
markspec lint <paths...> # info, warning, error output to stderr
markspec lint --format json <paths...> # structured JSON to stdout
markspec lint --strict <paths...> # promote warnings to errors (exit 1)
The lint subcommand is separate from validate; the pre-commit hook
(markspec hook) does not run lint — lint is a review-time quality gate,
not a commit blocker.