moonbit-refactoring/SKILL.md
Refactor MoonBit code to be idiomatic: shrink public APIs, convert functions to methods, use pattern matching with views, add loop invariants, and ensure test coverage without regressions. Use when updating MoonBit packages or refactoring MoonBit APIs, modules, or tests.
npx skillsauth add moonbitlang/moonbit-agent-guide moonbit-refactoringInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Start broad, then refine locally:
moon ide doc, moon ide find-references).moon check, then moon test.Avoid local cleanups (renaming, pattern matching) until the high-level structure is sound.
Treat files in MoonBit as organizational units; move code freely within a package as long as each file stays focused on one concept.
When spinning off package A into A and B:
Create the new package and re-export temporarily:
// In package B
using @A { ... } // re-export A's APIs
Ensure moon check passes before proceeding.
Find and update all call sites:
moon ide find-references <symbol>
Replace bare f with @B.f.
Remove the use statement once all call sites are updated.
Audit and remove newly-unused pub APIs from both packages.
internal/ package for helpers that shouldn't leak.pub from helpers; keep only required exports.internal/ packages to block external imports... for fluent, mutating chains when it reads clearly.Example:
// Before
fn reader_next(r : Reader) -> Char? { ... }
let ch = reader_next(r)
// After
#as_free_fn(reader_next, deprecated="Use Reader::next instead")
fn Reader::next(self : Reader) -> Char? { ... }
let ch = r.next()
To make the transition smooth, place #as_free_fn(old_name, ...) on the method; it emits a deprecated free function
old_name that forwards to the method.
Then you can check call sites and update them gradually by looking at warnings.
Example (chaining):
buf..write_string("#\\")..write_char(ch)
@pkg.fn instead of using when clarity matters.Example:
let n = @parser.parse_number(token)
When the expected type is known from context, you can omit the full package path for enum constructors:
Examples:
// Pattern matching - annotate the value being matched
let tree : @pkga.Tree = ...
match tree {
Leaf(x) => x
Node(left~, x, right~) => left.sum() + x + right.sum()
}
// Nested constructors - only outer needs full path
let x = @pkga.Tree::Node(left=Leaf(1), x=2, right=Leaf(3))
// Return type provides context
fn make_tree() -> @pkga.Tree {
Node(left=Leaf(1), x=2, right=Leaf(3))
}
// Collections - type annotation on the array
let trees : Array[@pkga.Tree] = [Leaf(1), Node(left=Leaf(2), x=3, right=Leaf(4))]
.. in the middle to match prefix and suffix at once.Array[Char].String/StringView indexing yields UInt16 code units. Use for ch in s for Unicode-aware iteration.For example,
match gen_results.get(0) {
Some(value) => Iter::singleton(value)
None => Iter::empty()
}
We can pattern match directly; it is often clearer and equally readable:
match gen_results {
[value, ..] => Iter::singleton(value)
[] => Iter::empty()
}
MoonBit pattern matching is pretty expressive, here are some more examples:
match items {
[] => ()
[head, ..tail] => handle(head, tail)
[..prefix, mid, ..suffix] => handle_mid(prefix, mid, suffix)
}
match s {
"" => ()
[.."let", ..rest] => handle_let(rest)
_ => ()
}
Use char literal overloading for Char, UInt16, and Int; the examples below rely on it. This is handy when matching String indexing results (UInt16) against a char range.
test {
let a_int : Int = 'b'
if (a_int is 'a'..<'z') { () } else { () }
let a_u16 : UInt16 = 'b'
if (a_u16 is 'a'..<'z') { () } else { () }
let a_char : Char = 'b'
if (a_char is 'a'..<'z') { () } else { () }
}
isis patterns inside if/guard to keep branches concise.Example:
match token {
Some(Ident([.."@", ..rest])) if process(rest) is Some(x) => handle_at(rest)
Some(Ident(name)) => handle_ident(name)
None => ()
}
Example:
// Before
let mut a = 1
let mut b = 2
for i = 0 {
if i >= n {
break
}
a = a + b
b = b + a
continue i + 1
}
for i = 0, a = 1, b = 2 {
if i >= n {
break a
}
continue i + 1, b, b + a
}
for _ in 0..<n; a = 1, b = 2 {
continue b, a + b
} nobreak {
a
}
for i in start..<end { ... }, for i in start..<=end { ... }, for i in large>..small, or for i in large>=..small for simple index loops.for x in xs {...} if xs is an iterator(e.g, Array) or for i,x in xs {...} if you also want to use the indexfor loops for algorithms that update state.Example:
// Before
for i = 0; i < len; i = i + 1{
items.push(fill)
}
// After
for i in 0..<len {
items.push(fill)
}
for x in xs loops.Example:
for i = 0, acc = 0; i < xs.length(); {
acc = acc + xs[i]
i = i + 1
} nobreak { acc }
where {
invariant: 0 <= i <= xs.length(),
reasoning: (
#| ... rigorous explanation ...
#| ...
)
}
*_test.mbt or *.mbt.md.mbt check fenced blocks for public APIs, and verify with moon check && moon test.Example:
///|
/// Return the last element of a non-empty array.
///
/// # Example
/// ```mbt check
/// test {
/// inspect(last([1, 2, 3]), content="3")
/// }
/// ```
pub fn last(xs : Array[Int]) -> Int { ... }
Commands:
moon coverage analyze -- -f summary
moon coverage analyze -- -f caret -F path/to/file.mbt
moon ide doc "<query>"
moon ide outline <dir|file>
moon ide find-references <symbol>
moon ide peek-def <symbol>
moon ide rename <symbol> <new_name>
moon ide analyze [path]
moon check
moon test
moon info
Use these commands for reliable refactoring.
Example: spinning off package_b from package_a.
Temporary import in package_b:
using @package_a { a, type B }
Steps:
moon ide find-references <symbol> to find all call sites of a and B.@package_a.a and @package_a.B.using statement and run moon check.development
Guide for migrating OCaml projects, libraries, modules, and test suites to idiomatic MoonBit. Use when translating OCaml code to MoonBit, planning a large OCaml-to-MoonBit port, preserving byte/string-heavy behavior, replacing OCaml variants/records/exceptions/refs/arrays, mapping OCaml APIs to MoonBit packages, or building verification and test strategy for a migration.
tools
Guide for writing, refactoring, and testing MoonBit projects. Use when working in MoonBit modules or packages, organizing MoonBit files, using moon tooling (build/check/run/test/doc/ide etc.), or following MoonBit-specific layout, documentation, and testing conventions.
development
Use when writing or refactoring proof-carrying code in MoonBit, especially for Why3-backed specifications, abstraction functions, representation invariants, proof assertions, recursive verified data structures, or reducing trusted proof bridges.
documentation
Guide for writing MoonBit bindings to C libraries using native FFI. Use when adding extern "c" declarations, writing C stubs with moonbit.h, configuring native-stub and link.native in moon.pkg, choosing