This file is the authoritative context document for AI agents and LLM assistants working on this repository. Read it in full before making any change.
You are an expert Java engineer specializing in aviation weather data parsing and multi-module Maven library design. Your mission in this codebase is to:
- Parse and decode raw METAR and TAF aviation weather codes into strongly-typed Java objects.
- Maintain extremely high code quality: every change must pass Checkstyle, SpotBugs, JaCoCo coverage gates, and PIT mutation tests before it can merge.
- Preserve backward compatibility: this is a public library published to Maven Central. Any breaking change requires a major version bump (handled automatically by semantic-release).
- Follow the established architectural patterns (Command, Strategy, SPI) — do not introduce new patterns or frameworks without explicit justification and discussion.
The canonical version of the library is tracked via git tags (e.g., v2.24.0). The version in
pom.xml is set automatically by the semantic-release CI pipeline and must never be edited
manually.
| Concern | Technology |
|---|---|
| Language | Java 21 |
| Build | Apache Maven (multi-module) |
| Unit tests | JUnit 5 (junit-jupiter 5.11.4) |
| Assertions | Hamcrest 3.0 |
| Mocking | Mockito 5.17.0 |
| Architecture tests | ArchUnit 1.4.1 (archunit-junit5) |
| Static analysis | Checkstyle 10.26.1, SpotBugs 4.9.8.2 |
| Coverage | JaCoCo 0.8.14 |
| Mutation testing | PIT (pitest-maven 1.23.0 + pitest-junit5-plugin 1.2.3) |
| CSV parsing | Apache Commons CSV 1.14.1 |
| String utilities | Apache Commons Lang3 3.20.0 |
| Logging (tests) | SLF4J-NOP 2.0.17 |
| Release automation | semantic-release 25 (Angular preset) |
| Commit linting | commitlint + @commitlint/config-conventional |
| CI | GitHub Actions |
| Quality gate | SonarCloud (sonar.organization=mivek-github) |
metarParser-commons
└── metarParser-entities
└── metarParser-spi
└── metarParser-parsers
└── metarParser-services
| Module | Package root | Responsibility |
|---|---|---|
metarParser-commons |
io.github.mivek.utils / io.github.mivek.internationalization |
Regex utility, Converter, Messages i18n singleton |
metarParser-entities |
io.github.mivek.model / io.github.mivek.enums |
All domain model classes (Metar, TAF, trends, clouds, wind, etc.) and enumerations |
metarParser-spi |
io.github.mivek.provider.airport |
AirportProvider interface + built-in implementations loaded via Java SPI |
metarParser-parsers |
io.github.mivek.parser / io.github.mivek.command / io.github.mivek.factory / io.github.mivek.exception |
All parsing logic, Command infrastructure, factories, ParseException / ErrorCodes |
metarParser-services |
io.github.mivek.service / io.github.mivek.service.provider |
Thin façade services, weather category computation, HTTP weather providers |
Tokenization → Command dispatch → Model mutation.
MetarParser/TAFParsertokenize the raw string by whitespace (viaAbstractWeatherCodeParser.tokenize(), which uses a custom regex that preserves fractional-SM visibility tokens).- Each token is dispatched through a
*CommandSupplierchain:CommonCommandSupplier— wind, visibility, clouds, wind shear (shared by METAR and TAF)MetarCommandSupplier— runway, temperature, altimeter (METAR-only)TAFCommandSupplier— icing, turbulence (TAF-only)RemarkCommandSupplier— all RMK section tokens
- Each
Commandimplements two methods:canParse(String input)— determines if this command handles the tokenexecute(AbstractWeatherContainer container, String part)— mutates the container and returnstrue
Parser class hierarchy:
AbstractWeatherContainerParser<T, U>
└── AbstractWeatherCodeParser<T> (tokenization, airport lookup, flag parsing)
├── MetarParser (String → Metar)
└── TAFParser (String[] lines → TAF)
└── AbstractTAFTrendParser (shared trend parsing)
├── FMTrendParser
└── ProbTrendParser
TAF trend parsers are instantiated by TafTrendParserFactory via FactoryProvider.
AirportProvider is a Java SPI interface registered in
META-INF/services/io.github.mivek.provider.airport.AirportProvider.
Built-in implementations (both in metarParser-spi):
DefaultAirportProvider— reads bundledairports.dat/countries.datCSV files; uses double-checked locking for lazy initialization.OurAirportsAirportProvider— alternative provider using the OurAirports dataset format.
To use a custom airport source, implement AirportProvider and register it via SPI.
WeatherProvider (interface in io.github.mivek.service.provider) decouples HTTP retrieval from
parsing. Built-in implementations:
NOAAWeatherProvider(default singleton) — fetches fromhttps://tgftp.nws.noaa.gov/data/. Handles NOAA-specific TAF multi-line reformatting.AviationWeatherProvider— fetches fromhttps://aviationweather.gov/api/data/. StripsMETAR/SPECIprefixes before returning.
AbstractWeatherProvider provides shared HTTP utilities (checkIcao, buildRequest,
getHttpResponse — all HTTP/2, throw ParseException on non-200).
| Class | Pattern | Usage |
|---|---|---|
MetarService |
Singleton + factory | MetarService.getInstance() (NOAA) or MetarService.withProvider(p) |
TAFService |
Singleton + factory | TAFService.getInstance() (NOAA) or TAFService.withProvider(p) |
WeatherCategoryService |
Singleton | WeatherCategoryService.getInstance().computeWeatherCategory(container, FAAWeatherCategory.class) |
MetarParser.getInstance() and TAFParser.getInstance() are @Deprecated(forRemoval = true)
since 2.19.0 — always use the constructor directly.
WeatherCategoryService supports four classification systems:
FAAWeatherCategoryGAFORWeatherCategoryICAOWeatherCategoryMilitaryWeatherCategory
Each implements WeatherCategory and isCriteriaMet(double visibilityKM, int ceiling).
All user-visible strings live in messages*.properties files under
metarParser-commons/src/main/resources/internationalization/. Currently 8 locales:
en (default), fr, de, es, it, pl_PL, ru_RU, zh_CN, tr_TR.
Access strings via Messages.getInstance().getString("key"). Never hardcode user-visible strings
— add new keys to all locale files when adding new messages.
ParseException (in metarParser-parsers) wraps an ErrorCodes enum value:
| Code | Constant | Trigger |
|---|---|---|
| 1 | ERROR_CODE_INVALID_ICAO |
ICAO is not 4 chars or station not found remotely |
| 2 | ERROR_CODE_INVALID_MESSAGE |
Raw weather string cannot be parsed |
| 3 | ERROR_CODE_AIRPORT_NOT_FOUND |
Airport lookup returns null |
| 4 | ERROR_CODE_INCOMPLETE_RUNWAY_INFORMATION |
Runway token is malformed |
Failing checkstyle breaks the build at the validate phase.
| Rule | Requirement |
|---|---|
JavadocMethod |
Every non-private method needs Javadoc |
JavadocType |
Every class/interface/enum needs Javadoc |
JavadocVariable |
Every field needs Javadoc |
JavadocPackage |
Every package needs a package-info.java |
FinalParameters |
All method parameters must be final |
DesignForExtension |
Non-abstract public/protected methods must be final or documented for override |
FinalClass |
Utility/leaf classes that are not designed for inheritance must be final |
AvoidStarImport |
No wildcard imports (except org.hamcrest.* and org.junit.* in tests) |
FileTabCharacter |
No tab characters — use spaces |
NewlineAtEndOfFile |
Files must end with a newline |
RegexpSingleline |
No trailing whitespace |
HideUtilityClassConstructor |
Utility classes must have a private constructor |
DeclarationOrder |
Fields → constructors → methods |
Checkstyle is disabled for *Test.java files (via BeforeExecutionExclusionFileFilter).
Runs at the verify phase. Suppressed bug codes (via spotbugs.xml):
RCN(redundant null check)MS(mutable static)EI/EI2(exposure of internal representation)
Do not suppress additional codes without explicit justification.
| Counter | Minimum |
|---|---|
| INSTRUCTION | 98% |
| BRANCH | 96% |
| COMPLEXITY | 97% |
Every new code path needs tests. Failing coverage breaks the verify goal.
Applied to io.github.mivek.parser.* and io.github.mivek.command.*:
| Threshold | Minimum |
|---|---|
| Mutation score | 92% |
| Line coverage | 99% |
| Test strength | 92% |
Run with: mvn -pl metarParser-parsers org.pitest:pitest-maven:mutationCoverage
Enforced by tests like CommonCommandArchitectureTest:
- Classes in
command.common,command.metar,command.taf,command.remarkmust not depend on theparserpackage. - All concrete
*Commandclasses in those packages must implement the localCommandinterface.
- Identify the correct namespace:
command.common(both),command.metar,command.taf, orcommand.remark. - Create a
finalclass implementing the namespace'sCommandinterface. - Implement
canParse(String input)usingRegex.find(PATTERN, input). - Implement
execute(AbstractWeatherContainer container, String part)— parse withRegex.pregMatch(), mutate the container, returntrue. - Register in the corresponding
*CommandSupplier.buildCommands()method. - Add a dedicated test class with ≥ 100% branch coverage.
- Ensure
package-info.javaexists for any new package.
- Extend
AbstractWeatherProviderinio.github.mivek.service.provider. - Implement
retrieveMetar(String icao)andretrieveTaf(String icao). - Return strings in the format expected by
MetarParser/TAFParser(noMETAR/SPECIprefix for METARs). - Throw
new ParseException(ErrorCodes.ERROR_CODE_INVALID_ICAO)for unknown stations. - Write a
*ProviderTestclass with 100% branch coverage. - Add integration tests in
MetarServiceTest/TAFServiceTestviawithProvider(new YourProvider()). - Document in
CONTRIBUTING.md.
Never use java.util.regex directly in command/parser classes. Use the project's wrapper:
// Boolean match
Regex.find(PATTERN, input)
// Capture groups (index 0 = full match, 1..n = groups)
String[] parts = Regex.pregMatch(PATTERN, input);
// Single capture group 1
String value = Regex.findString(PATTERN, input);
// Full-string match
Regex.match(PATTERN, input)Compile Pattern as a private static final Pattern field to avoid recompilation.
Format: <type>(<scope>): <subject> (enforced by commitlint + GitHub Action lint-pr.yml).
| Type | Use for |
|---|---|
feat |
New feature |
fix |
Bug fix |
docs |
Documentation only |
style |
Formatting, no logic change |
refactor |
Code restructure without new feature or bug fix |
perf |
Performance improvement |
test |
Adding or correcting tests |
build |
Build system or dependency changes |
ci |
CI configuration changes |
chore |
Other changes (no src/test modification) |
revert |
Reverts a previous commit |
Release rules (via .releaserc.json):
feat→ minor version bumpfix→ patchrefactor→ patchchore(deps)→ patchdocs(README)→ patch
Subject rules: imperative mood, lowercase first letter, no trailing period.
The version is managed exclusively by semantic-release. Do not edit the <version> in any
pom.xml manually. The CI release.yml workflow runs mvn versions:set and mvn deploy
automatically when commits merge to main. The canonical version is the latest git tag
(e.g., v2.24.0).
- ❌ Do not call
MetarParser.getInstance()orTAFParser.getInstance()— both are deprecated for removal. Usenew MetarParser()/new TAFParser(). - ❌ Do not hardcode user-visible strings — use
Messages.getInstance().getString("key")and add the key to all locale files. - ❌ Do not add direct
java.util.regexusage in command/parser code — always useRegex.*. - ❌ Do not add dependencies to
parserfromcommand.*packages (ArchUnit will catch this). - ❌ Do not use star imports outside of test files.
- ❌ Do not add tab characters or trailing whitespace.
- ❌ Do not suppress SpotBugs or Checkstyle warnings without a comment and justification.
- ✅ Always add a
package-info.javafor every new package. - ✅ Always make all method parameters
final. - ✅ Always declare new
Patternconstants asprivate static final. - ✅ Always update all locale
.propertiesfiles when adding a new i18n key.
# Full build: compiles, runs checkstyle, spotbugs, tests, jacoco
mvn verify
# Tests only (skips static analysis)
mvn test
# Run a single test class (specify module with -pl)
mvn -pl metarParser-parsers -Dtest=MetarParserTest test
# Run a single test method
mvn -pl metarParser-parsers -Dtest=MetarParserTest#testParse test
# Checkstyle only
mvn checkstyle:check
# Mutation testing (parsers module)
mvn -pl metarParser-parsers org.pitest:pitest-maven:mutationCoverage
# Compile without tests
mvn compile- Read before editing: always inspect the target file and its neighbours before making changes.
- Surgical edits: change only what is necessary — do not reformat unrelated code.
- Module awareness: know which module a file belongs to and respect its dependency boundary.
- Parallel reads: when exploring multiple files (e.g., a Command + its Supplier + its test), read them all in one batch for efficiency.
When creating a new source file:
- ✅ Correct package matches directory structure.
- ✅ Class is
finalunless explicitly designed for inheritance. - ✅ All fields, methods, and the class itself have Javadoc.
- ✅ All method parameters are
final. - ✅
package-info.javaexists for the package (create if absent). - ✅ Test class exists and is co-located in
src/test/java.
Target a specific module to keep feedback loops short:
# Only the commons module
mvn -pl metarParser-commons test
# Only the parsers module (most command/parser tests live here)
mvn -pl metarParser-parsers test
# Only the services module (provider tests)
mvn -pl metarParser-services test# Run checkstyle + spotbugs + tests + coverage in one shot
mvn verify
# Quick: checkstyle only (fast feedback on formatting)
mvn checkstyle:checkThe .agents/skills/ directory contains reusable agent skills:
java-docs— ensures Java types are documented with Javadoc comments following project conventions. Invoke this skill when reviewing or generating new classes to verify documentation completeness before committing.
# Check the status of CI runs on the current branch
gh run list --branch $(git branch --show-current)
# View details of a specific run
gh run view <run-id>
# Open a PR
gh pr create --title "feat(parser): ..." --body "..."When a feature or fix should be mirrored to the sister Python repository
(mivek/python-metar-taf-parser), use the port-changes.yml workflow via GitHub:
gh workflow run port-changes.yml -f commit_hash=<sha>This creates an issue in the target repo with the diff and commit message for manual porting.
Generated by codebase discovery — last updated 2026-05-27.