
Ears Requirements
- 31 installs
- 1 repo stars
- Updated July 31, 2026
- hexbee/hello-skills
Rewrites ambiguous natural-language requirements into structured EARS statements and classifies each into its EARS pattern.
About
Transforms requirement drafts into concise EARS-compliant statements using strict clause order and one clear system response per requirement. A developer uses it when converting vague requirements into testable, unambiguous statements and reviewing them for missing triggers and states.
- Classifies requirements into ubiquitous, state-driven, event-driven, optional-feature, and unwanted-behavior patterns
- Bundled validate_ears.py script classifies patterns and catches syntax and quality issues
Ears Requirements by the numbers
- 31 all-time installs (skills.sh)
- +2 installs in the week ending Aug 2, 2026 (Skillselion tracking)
- Ranked #929 of 1,879 Documentation skills by installs in the Skillselion catalog
- Data as of Aug 2, 2026 (Skillselion catalog sync)
npx skills add https://github.com/hexbee/hello-skills --skill ears-requirementsAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 31 |
|---|---|
| repo stars | ★ 1 |
| Last updated | July 31, 2026 |
| Repository | hexbee/hello-skills ↗ |
What it does
Rewrites ambiguous natural-language requirements into structured EARS statements and classifies each into its EARS pattern.
Files
Ears Requirements
Overview
Transform requirement drafts into concise EARS-compliant statements, preserving intent while reducing ambiguity.
Workflow
1. Extract requirement intent from user input. 2. Identify the correct EARS pattern:
- Ubiquitous
- State-driven
- Event-driven
- Optional-feature
- Unwanted-behavior
- Complex combinations
3. Rewrite each requirement using strict clause order and one clear system response. 4. Run a quality pass for measurability, testability, and missing conditions. 5. Return:
- Rewritten requirement(s)
- Pattern label for each
- Brief rationale if pattern choice could be disputed
Authoring Rules
- Keep one requirement per statement.
- Use exactly one explicit system subject (for example: "the ATM").
- Use
shallfor mandatory behavior. - Prefer observable outcomes over implementation details.
- Keep conditions explicit; avoid implied triggers or hidden states.
- Avoid weak phrases such as "as appropriate", "if possible", "etc.".
- If numeric limits or timing are unknown, add a clear placeholder token (for example:
<MAX_LATENCY_MS>).
EARS Clause Order
Apply only the clauses needed by the chosen pattern, always in this order:
While <state/precondition>, when <trigger>, the <system> shall <response>
Use unwanted behavior pattern as:
If <undesired trigger>, then the <system> shall <response>
For pattern definitions and examples, read references/ears-patterns.md.
Scripts
Use scripts/validate_ears.py to classify pattern and catch syntax/quality issues quickly.
Single requirement:
python3 scripts/validate_ears.py --requirement "When mute is selected, the laptop shall suppress all audio output."
Batch file (one requirement per line):
python3 scripts/validate_ears.py --file requirements.txt
Machine-readable output:
python3 scripts/validate_ears.py --file requirements.txt --json
Quality Gate
Before finalizing, verify each requirement:
- Is testable with a pass/fail criterion.
- Has unambiguous actor, condition, and response.
- Uses consistent terminology with no synonym drift.
- Avoids combining multiple independent behaviors unless explicitly complex.
- Matches the selected EARS pattern.
If any check fails, provide a corrected version and explain the minimal change made.
interface:
display_name: "EARS Requirements"
short_description: "Write requirements with EARS patterns"
default_prompt: "Convert and author system requirements using EARS syntax and quality checks."
interface:
display_name: "EARS Requirements"
short_description: "Write structured requirements using EARS syntax"
default_prompt: "Convert my requirements into EARS format, label the pattern for each requirement, and flag ambiguity or missing trigger/state/response details."
EARS Pattern Reference
Pattern Templates
- Ubiquitous:
The <system> shall <response>. - State-driven:
While <precondition>, the <system> shall <response>. - Event-driven:
When <trigger>, the <system> shall <response>. - Optional-feature:
Where <feature is included>, the <system> shall <response>. - Unwanted-behavior:
If <undesired trigger>, then the <system> shall <response>. - Complex:
While <precondition>, when <trigger>, the <system> shall <response>.
Pattern Selection Heuristic
- Choose Ubiquitous when behavior is always true.
- Choose State-driven when behavior is active only in a continuous condition.
- Choose Event-driven when behavior is caused by a discrete event.
- Choose Optional-feature when requirement applies only if a feature exists.
- Choose Unwanted-behavior when specifying mitigation or recovery.
- Choose Complex when both state and event are required to activate behavior.
Examples
- Ubiquitous:
The mobile phone shall have a mass of less than <MAX_GRAMS> grams. - State-driven:
While there is no card in the ATM, the ATM shall display "insert card to begin". - Event-driven:
When mute is selected, the laptop shall suppress all audio output. - Optional-feature:
Where the car has a sunroof, the car shall provide a sunroof control panel on the driver door. - Unwanted-behavior:
If an invalid credit card number is entered, then the website shall prompt for re-entry of credit card details. - Complex:
While the aircraft is on ground, when reverse thrust is commanded, the engine control system shall enable reverse thrust.
Anti-Patterns
- Multiple behaviors in one statement joined by "and".
- Missing system subject.
- Missing trigger for event-driven behavior.
- Non-testable language: "quickly", "user-friendly", "normally".
- Mixed keyword order (for example
When ..., while ...in the same clause chain).
Rewrite Checklist
- Identify actor/system.
- Identify state or trigger (if any).
- Identify mandatory response.
- Select pattern and template.
- Rewrite in EARS form.
- Verify objective and testable wording.
#!/usr/bin/env python3
"""Validate EARS requirements and classify their pattern."""
import argparse
import json
import re
import sys
from pathlib import Path
from typing import List
WEAK_TERMS = [
"as appropriate",
"if possible",
"etc",
"normally",
"quickly",
"user-friendly",
]
def classify_pattern(text: str) -> str:
lowered = text.strip().lower()
if lowered.startswith("if "):
return "unwanted-behavior"
if lowered.startswith("while ") and " when " in lowered:
if lowered.find("while ") < lowered.find(" when "):
return "complex"
if lowered.startswith("while "):
return "state-driven"
if lowered.startswith("when "):
return "event-driven"
if lowered.startswith("where "):
return "optional-feature"
if lowered.startswith("the "):
return "ubiquitous"
return "unknown"
def validate_requirement(text: str) -> dict:
issues = []
lowered = text.lower().strip()
pattern = classify_pattern(text)
shall_count = len(re.findall(r"\bshall\b", lowered))
if shall_count == 0:
issues.append("Missing required keyword 'shall'.")
elif shall_count > 1:
issues.append("Use one behavior per statement: multiple 'shall' found.")
if pattern == "unwanted-behavior":
if not re.search(r"^if\s+.+,\s*then\s+the\s+.+\s+shall\s+.+", lowered):
issues.append("Unwanted-behavior form should be: If ..., then the <system> shall ...")
elif pattern == "complex":
if not re.search(r"^while\s+.+,\s*when\s+.+,\s*the\s+.+\s+shall\s+.+", lowered):
issues.append("Complex form should be: While ..., when ..., the <system> shall ...")
elif pattern == "state-driven":
if not re.search(r"^while\s+.+,\s*the\s+.+\s+shall\s+.+", lowered):
issues.append("State-driven form should be: While ..., the <system> shall ...")
elif pattern == "event-driven":
if not re.search(r"^when\s+.+,\s*the\s+.+\s+shall\s+.+", lowered):
issues.append("Event-driven form should be: When ..., the <system> shall ...")
elif pattern == "optional-feature":
if not re.search(r"^where\s+.+,\s*the\s+.+\s+shall\s+.+", lowered):
issues.append("Optional-feature form should be: Where ..., the <system> shall ...")
elif pattern == "ubiquitous":
if not re.search(r"^the\s+.+\s+shall\s+.+", lowered):
issues.append("Ubiquitous form should be: The <system> shall ...")
else:
issues.append("Cannot classify EARS pattern from leading clause.")
if " if " in lowered and pattern != "unwanted-behavior":
issues.append("Use 'If ..., then ...' only for unwanted-behavior requirements.")
if " while " in lowered and " when " in lowered and lowered.find(" when ") < lowered.find(" while "):
issues.append("Clause order violation: place 'While ...' before 'when ...'.")
for term in WEAK_TERMS:
if term in lowered:
issues.append(f"Avoid weak wording: '{term}'.")
if " and " in lowered and shall_count >= 1:
issues.append("Potential multiple behaviors in one requirement; split if independent.")
return {
"requirement": text,
"pattern": pattern,
"valid": len(issues) == 0,
"issues": issues,
}
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(description="Validate EARS requirement statements.")
parser.add_argument("--requirement", help="Single requirement text to validate.")
parser.add_argument("--file", help="Path to a text file with one requirement per line.")
parser.add_argument("--json", action="store_true", help="Output JSON.")
return parser.parse_args()
def load_requirements(args: argparse.Namespace) -> List[str]:
if args.requirement and args.file:
print("Use either --requirement or --file, not both.", file=sys.stderr)
sys.exit(2)
if not args.requirement and not args.file:
print("Provide --requirement or --file.", file=sys.stderr)
sys.exit(2)
if args.requirement:
return [args.requirement.strip()]
path = Path(args.file)
if not path.exists():
print(f"File not found: {path}", file=sys.stderr)
sys.exit(2)
lines = [line.strip() for line in path.read_text(encoding="utf-8").splitlines()]
return [line for line in lines if line]
def main() -> None:
args = parse_args()
requirements = load_requirements(args)
results = [validate_requirement(item) for item in requirements]
if args.json:
print(json.dumps(results, ensure_ascii=True, indent=2))
return
for idx, result in enumerate(results, start=1):
status = "PASS" if result["valid"] else "FAIL"
print(f"[{status}] #{idx} pattern={result['pattern']}")
print(f" {result['requirement']}")
if result["issues"]:
for issue in result["issues"]:
print(f" - {issue}")
if __name__ == "__main__":
main()