Skip to content

Latest commit

 

History

40 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

MoonGuard

MoonGuard is a MoonBit public API compatibility and SemVer guard. It compares two MoonBit interface snapshots, reports public API additions, removals, and signature changes, then recommends whether the next release should be patch, minor, or major.

The project is being built for the MoonBit open-source ecosystem competition as engineering infrastructure for package authors and CI workflows.

Review Materials

The competition submission, validation evidence, design report, compatibility rules, and package-release checklist are indexed in docs/README.md. This index is the recommended starting point for reviewers.

Current Features

  • Parse public declarations from .mbti-style interface text.
  • Normalize whitespace and declaration order.
  • Track public fn, type, typealias, struct, enum, trait, impl, let, and const declarations.
  • Track common generated .mbti members including struct fields, enum constructors, trait methods, generic methods, suberror, and pub using type or trait exports.
  • Classify public API changes:
    • added top-level public API -> minor
    • added struct fields, enum/suberror constructors, or required trait methods -> major (conservative)
    • removed public API -> major
    • changed signature -> major
    • unchanged public API -> patch
  • Render Markdown and JSON compatibility reports for PR comments, release notes, and downstream tooling.
  • Validate whether a proposed SemVer bump satisfies the recommended impact for single-file or package-directory comparisons.
  • Evaluate compatibility exceptions with auditable policy rules that retain the original report, accepted changes, reasons, deadlines, match budgets, and diagnostics. Legacy ignore rules remain available for compatibility.
  • Share repeated CLI defaults through simple config files.
  • Compare package directories, detect duplicate symbols and directory diagnostics, ignore generated/vendor/tool directories, and render snapshot inventories.
  • Identify directory APIs by parent-directory package scope plus symbol, so moving a declaration between .mbti files inside one package is not reported as an API change while same-named symbols in different packages remain distinct.
  • Read .mbti files and directories from the CLI when running on the JS backend.
  • Render package release plans that combine API impact, diagnostics, SemVer validation, and maintainer checklist items for release PRs.

Installation

Install the library from mooncakes.io:

moon add 918154429/moonguard@0.2.0

Install the command-line package:

moon install 918154429/moonguard/cmd/main@0.2.0

For source development, install the MoonBit toolchain, then clone this repository:

git clone https://github.com/918154429/moonguard.git
cd moonguard
moon check
moon test
moon test --target js

Library Usage

let old_api = "pub fn render(String) -> String"
let new_api = "pub fn render(String, Options) -> String"

let report = @moonguard.diff_interfaces(old_api, new_api)
let markdown = @moonguard.render_markdown_report(report)

The report recommendation is patch, minor, or major.

CLI Usage

Compare two .mbti files with the default Markdown report:

moon run --target js cmd/main -- report fixtures/old.mbti fixtures/new.mbti

Output:

# MoonGuard API Compatibility Report

- Recommendation: **major**
- Changes: 2

| Impact | Change | Symbol | Details |
| --- | --- | --- | --- |
| major | changed | `fn render` | `render(String) -> String` -> `render(String, Options) -> String` |
| minor | added | `fn parse` | `parse(String) -> Unit` |

Render the same report as JSON:

moon run --target js cmd/main -- report fixtures/old.mbti fixtures/new.mbti --format json

Check whether a planned version bump is sufficient:

moon run --target js cmd/main -- check fixtures/old.mbti fixtures/new.mbti --current 0.1.0 --next 0.2.0

Use a config file for repeated CI defaults:

format = json
current = 0.1.0
next = 1.0.0
baseline = fixtures/old.mbti
target = fixtures/new.mbti
baseline_dir = fixtures/dir-old
target_dir = fixtures/dir-new
policy_file = fixtures/allow-render.policy
policy_version = 0.2.0
moon run --target js cmd/main -- report --config fixtures/moonguard-ci.conf
moon run --target js cmd/main -- check --config fixtures/moonguard-ci.conf

Compare package directories and inspect a generated interface inventory:

moon run --target js cmd/main -- report-dir fixtures/dir-old fixtures/dir-new
moon run --target js cmd/main -- check-dir fixtures/dir-old fixtures/dir-new --current 0.1.0 --next 1.0.0
moon run --target js cmd/main -- release-plan fixtures/dir-old fixtures/dir-new --current 0.1.0 --next 1.0.0
moon run --target js cmd/main -- inventory-dir fixtures/dir-new --format json
moon run --target js cmd/main -- report-dir --config fixtures/moonguard-ci.conf
moon run --target js cmd/main -- check-dir --config fixtures/moonguard-ci.conf
moon run --target js cmd/main -- release-plan --config fixtures/moonguard-ci.conf
moon run --target js cmd/main -- inventory-dir --config fixtures/moonguard-ci.conf

The release-plan command is intended for application-facing package workflows: it prints whether a release is ready, blocked by snapshot diagnostics, or needs a larger version bump, then includes checklist items for release notes and migration review.

Ignore files filter report changes without changing the parsed API model:

ignore fn debug_tmp temporary demo API
ignore field Options.experimental
ignore * pkg.generated.mbti::internal_*
moon run --target js cmd/main -- report fixtures/old.mbti fixtures/new.mbti --ignore-file fixtures/ignore-render.rules

For release governance, prefer an auditable policy file:

allow changed fn render until 0.2.0 max_matches 1 reason render migration reviewed
allow removed fn legacy_* max_matches 2 reason legacy cleanup approved

The syntax is allow CHANGE_KIND ITEM_KIND NAME [until VERSION] [max_matches N] reason TEXT.... CHANGE_KIND is added, removed, changed, or *; item kind and name also support * matching. A non-empty reason is mandatory and max_matches defaults to 1. Expired rules, malformed rules, missing policy versions, and exceeded budgets fail closed with exit code 2. Unmatched and overlapping rules remain visible as warnings.

moon run --target js cmd/main -- report fixtures/old.mbti fixtures/new.mbti --policy-file fixtures/allow-render.policy --policy-version 0.2.0
moon run --target js cmd/main -- check fixtures/old.mbti fixtures/new.mbti --current 0.1.0 --next 0.2.0 --policy-file fixtures/allow-render.policy

Policy output preserves both original_report and effective_report, plus the accepted changes and rule diagnostics. check, check-dir, and release-plan use --next as the policy version when --policy-version is omitted. --ignore-file and --policy-file are mutually exclusive, and inventory-dir does not accept policy files.

File and directory commands currently require the JS backend because the CLI uses Node fs.readFileSync and directory APIs through MoonBit JS externs. For strict CI exit-code checks, run the generated JS with Node directly after a JS-target command has built it:

node _build/js/debug/build/cmd/main/main.js check fixtures/old.mbti fixtures/new.mbti --current 0.1.0 --next 0.2.0
node _build/js/debug/build/cmd/main/main.js check-dir fixtures/dir-old fixtures/dir-new --current 0.1.0 --next 1.0.0

For quick demos without files, use report-text:

moon run cmd/main -- report-text "pub fn render(String) -> String" "pub fn render(String, Options) -> String" --format json

GitHub Actions

MoonGuard can compare a committed release baseline with interfaces generated in a pull request, fail on an insufficient version bump, and upload a JSON release plan. See docs/github-actions.md for a complete downstream workflow pinned to the v0.1.0 release.

Compatibility Rules

MoonGuard starts with conservative rules:

  • Removing a public declaration is a breaking change.
  • Changing a public signature is a breaking change.
  • Adding an ordinary top-level public declaration is minor-compatible.
  • Adding a struct field, enum/suberror constructor, or required trait method is conservatively breaking because it can invalidate construction, exhaustive matching, or existing trait implementations.
  • Reordering declarations, changing comments, or changing whitespace does not affect the public API model.

The parser intentionally covers high-frequency .mbti declarations first, including common nested members from generated interface files. Any unrecognized pub line is retained as unknown so that public surface changes remain visible instead of being silently ignored.

For directory snapshots, a symbol's logical identity is its parent-directory scope plus its declaration identity; the .mbti filename is provenance, not identity. Therefore moving pkg/a.mbti::render to pkg/b.mbti::render produces no compatibility change. Moving it to another package scope does change its logical name, and duplicate definitions within one package are still diagnosed.

The complete rule table and rationale are in docs/api-compat-rules.md.

Real-World Validation

The repository includes 15 pinned public pkg.generated.mbti samples from official and community MoonBit projects. MoonGuard currently extracts 6819 API items from this corpus with zero unknown declarations and zero snapshot diagnostics. All 15 samples are fully modeled, including one historical sample whose generated interface contains 119 associated fn/impl lines without a public visibility prefix. Legacy inference is restricted to files identified by the moon info generator header and package declaration.

Development

The implementation is split by responsibility: api_model.mbt, parser.mbt, snapshot.mbt, diff.mbt, semver.mbt, policy.mbt, report_markdown.mbt, and report_json.mbt. The CLI is likewise separated into argument parsing, configuration, commands, output, snapshot I/O, and backend-specific file I/O under cmd/main.

Current validation result: 164/164 default-target tests and 165/165 JS-target tests pass.

Common checks:

moon fmt
moon info
moon check
moon test
moon test --target js
moon run --target js cmd/main -- report fixtures/old.mbti fixtures/new.mbti --format json
moon run --target js cmd/main -- report --config fixtures/moonguard-ci.conf
moon run --target js cmd/main -- report-dir fixtures/dir-old fixtures/dir-new
moon run --target js cmd/main -- report-dir --config fixtures/moonguard-ci.conf
moon run --target js cmd/main -- check-dir fixtures/dir-old fixtures/dir-new --current 0.1.0 --next 1.0.0
moon run --target js cmd/main -- check-dir --config fixtures/moonguard-ci.conf
moon run --target js cmd/main -- release-plan --config fixtures/moonguard-ci.conf
moon run --target js cmd/main -- inventory-dir --config fixtures/moonguard-ci.conf
node _build/js/debug/build/cmd/main/main.js check fixtures/old.mbti fixtures/new.mbti --current 0.1.0 --next 0.2.0
node _build/js/debug/build/cmd/main/main.js check --config fixtures/moonguard-ci.conf
node _build/js/debug/build/cmd/main/main.js check-dir fixtures/dir-old fixtures/dir-new --current 0.1.0 --next 1.0.0
node _build/js/debug/build/cmd/main/main.js check-dir --config fixtures/moonguard-ci.conf

Generated pkg.generated.mbti files are kept in the repository so interface changes are reviewable after moon info.

Competition source-line tracking counts repository .mbt source files and excludes generated _build output. The current tracked source total is 8580 lines, including 5215 non-test lines. The confirmed competition threshold is 4000 lines.

See docs/competition-plan.md for the competition plan.

About

MoonBit public API compatibility and SemVer guard

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages