moonbitlang/moonbit-agent-guide/moonbit-proof
Claude Code and agent skill by moonbitlang/moonbit-agent-guide
- community
Checking that the SKILL.md file is still in the repository.
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.
Install
npx vskill@latest install moonbitlang/moonbit-agent-guide/moonbit-proofSKILL.md
MoonBit Proof-Carrying Code
Use this skill when the task is to write, extend, or debug verified MoonBit code.
Typical triggers:
- add contracts to executable MoonBit code
- define an abstract model or representation invariant
- verify a recursive data structure
- connect a concrete representation to a set/map model
- replace trusted proof bridges with lemmas
- debug proof failures, timeouts, or frontend lowering limits
Goal
Write proof-carrying code, not proof-shaped comments.
That means:
- the runtime code remains executable and readable
- the proof model is explicit
- contracts talk about named predicates/functions with consistent roles
- local
proof_assertsteps explain why the implementation satisfies the model
Naming
Prefer model(...) as the default name for the proof-side semantic view of a value.
Examples:
model(set) : Fset[Int]model(map) : Fmap[Int, Int]model(tree) : Seq[Int]
Use a more specific name only when it is materially clearer:
elements(node)when the model is literally the set of elements stored in a recursive subtreedomain(bitmap)when the model is specifically the occupied index setheight(tree)when it is a structural measure, not the main semantic model
Default rule:
- use
modelfor the main semantic abstraction - use specialized names like
elements,domain,height, orrankfor auxiliary views
Default Structure
Split the package into two layers.
.mbtp- model functions such as
model(x) - representation invariants such as
tree_inv(x)orsparse_array_inv(x) - named proof predicates such as
insert_pre(...)andinsert_post(...) - reusable lemmas
.mbt- executable code
- contracts over the proof-side predicates
- local
proof_assertsteps after construction, branching, and loops
Use this default unless there is a strong reason not to.
Recommended Workflow
- Choose the abstract model first.
- Define the smallest useful invariant.
- State contracts with named
*_pre/*_postpredicates. - Implement the runtime code.
- Add loop invariants for every proof-relevant loop.
- Add local
proof_assertsteps where the solver needs help. - Introduce helper lemmas only after seeing actual failing VCs.
- Shrink trusted bridges from constructors outward.
Do not start by writing a large pile of lemmas.
Step 1: Pick the Right Abstract Model
Prefer the simplest model that matches the observable behavior.
- Membership-only structure: use a finite set.
- Key-value structure: use a finite map.
- Recursive tree/trie: use
model(node)or, if clearer,elements(node). - Packed layout: separate domain/layout facts from semantic meaning.
Example:
If a recursive helper really denotes the element set of a subtree, this is also reasonable:
Avoid putting the whole semantics directly into every contract.
Step 2: Keep the Invariant Small
The invariant should mostly describe:
- shape
- bounds
- layout
- well-formedness
Semantic equalities usually belong in postconditions or lemmas, not inside *_inv.
Good:
Not good:
- putting every update theorem into the invariant
- encoding the entire semantic equality into
*_inv
Step 3: Use Named Postconditions
Prefer named predicates over repeating large formulas. As a default naming convention, use *_inv, *_pre, and *_post.
Good:
This keeps contracts short and gives the solver a reusable target.
Step 4: Put the Math in .mbtp
Proof-side material belongs in .mbtp.
Examples:
Keep .mbtp focused on:
- logic definitions
- predicates
- lemmas
Avoid filling .mbtp with runtime implementation details.
Two recurring helper patterns are especially useful:
- Extensional equality hypotheses for abstract structures.
Example:
This is often the cleanest way to finish map-refinement proofs.
- Small transport lemmas for updates.
Examples:
- add/remove
memat self and other keys - add/remove
findat self and other keys - set cardinality after adding/removing an absent/present element
Prefer several small transport lemmas over one giant “everything changed correctly” theorem.
Step 5: Guide the Solver in .mbt
After constructing data, assert the concrete facts the solver may miss.
Example:
Use proof_assert:
- after record construction
- after array writes
- after case splits
- after loop bodies establish a stronger relation
Prefer this over introducing a callable trusted wrapper function.
Step 6: Write Loop Invariants Early
Any loop that is relevant to the proof should get invariants as soon as the loop shape stabilizes.
In practice, proof-carrying MoonBit code often relies on loops for:
- copying array prefixes or suffixes
- accumulating counts or ranks
- building a result structure incrementally
- iterating over a subtree or packed representation
Do not wait for the prover to fail before writing the obvious invariants.
Typical invariants:
- index bounds
- relationship between the accumulator and the abstract model so far
- prefix/suffix copy facts
- preservation of unchanged fields
Example:
For array updates, use staged invariants that match the proof shape.
Example:
Then strengthen to a second invariant after the inserted/removed element is handled.
Default rule:
- if a loop contributes to a postcondition, its invariant should mention the proof-side progress explicitly
- if a loop only mutates concrete state, the invariant should still state the concrete relation needed by the next abstraction lemma
- if the loop's final yielded value matters semantically, add
proof_yieldso the prover knows what the yielded result satisfies
Example:
Use proof_yield when the proof needs a fact about the value produced by the whole loop expression, not just the state maintained during iteration.
Step 7: Verify the Natural API Surface
If the public API is method-oriented, verify the methods directly.
Example:
Use top-level verified helper functions only when they improve structure or reuse, not as a workaround for method contracts.
Step 8: Use Structural Proof Shape for Recursive Code
For recursive data structures:
- define a semantic view like
model(node)orelements(node) - define a shape invariant like
node_ok(node, level) - recurse structurally
- add
proof_decrease
Example:
If the solver resists tail-recursive loops in contracted functions, try structurally recursive code first.
Step 9: For Packed or Indexed Representations, Prove Concrete Updates Before Semantic Meaning
When a representation is packed, indexed, or incrementally rebuilt, do not jump straight from low-level mutation to the final semantic theorem.
First prove concrete update facts that match the implementation structure, then connect them to the abstract model.
A common progression is:
- basic domain or indexing facts
- local bounds or position facts
- concrete update facts for unchanged and changed regions
- a full concrete-update predicate
- the final semantic
*_posttheorem
The exact intermediate predicates depend on the implementation. Choose names that reflect the actual stages in the code.
Typical stages are:
- unchanged region
- updated region
- shifted or rebuilt region
- full concrete-update predicate
- final semantic postcondition
Example pattern:
For sparse or dense-array code, a more specific ladder like *_prefix_ok, *_fill_ok, and *_data_ok is often effective, but treat that as one useful instance of the general technique rather than a universal template.
Step 10: Keep Shared Shim Packages Small
If you have reusable proof imports or theories, put them in shim packages.
Typical examples:
- finite-set wrappers
- finite-map wrappers
- bitmap domain/rank/count helpers
The benefit is:
- client packages stay focused
- imports are not duplicated
- shared reasoning is easier to test for regressions
But keep shared shims minimal. Large shared lemma sets can perturb unrelated proofs.
Also account for lowering quirks:
- methods may work in contracts while static constructors do not
- a free wrapper like
fmap_mk(...)may still be needed even ifFmap::mk(...)parses - keep those wrappers in the shim package, not duplicated in every client
If a helper is only needed by one package, prefer a local lemma there rather than exporting it from a shared shim.
Step 11: Treat Trust as Temporary
Trusted helpers are acceptable as narrow bridges, but they should not be the design endpoint.
This is the start of the file. Read the full SKILL.md on GitHub.
Security
- Security scanPassed, 100 of 100Pattern checks on the skill's files.
- Model reviewNot runA model review is scheduled for the next rescan.
Quality evaluation
- Task evaluationNot runNo evaluation yet. Run one in Skill Studio.
Source
- SKILL.md in the repositoryCheckingmoonbit-proof/SKILL.md
Popularity
- Repository stars
- 75
- Repository forks
- 11
- 7-day trend score
- 22
Stars and forks are for the whole GitHub repository, not this skill alone. Not refreshed yet.
Works with
Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.
- Claude Code
- Cursor
- GitHub Copilot
- Windsurf
- Codex
- Gemini CLI
- Cline
- Amp
- Roo Code
- Goose
- Continue
- Kilo Code
- Junie
- Trae
- OpenHands
- Qwen Code
- AI Agent
- Tabnine Chat
- Sourcegraph Cody
- Amazon Q
- Firebase Genkit
- Sweep
- Devin
- Phind
- Mentat
- Claude Desktop
- Aider
- GPT Pilot
- Plandex
- bolt.new
- v0
- Replit Agent
- Anse
- TypingMind
- BetterChatGPT
- LibreChat
- Warp AI
- Pieces
- Fig AI
Embed badge
[](https://verified-skill.com/skills/moonbitlang/moonbit-agent-guide/moonbit-proof)