Mops Package Maintenance
What This Is
A step-by-step playbook for an AI agent to fully maintain a Motoko package published on MOPS: upgrade dependencies, validate the package, polish docs, format the code, ensure CI, and prepare a versioned release branch — all in one automated pass.
When to Use
- Periodic dependency-bump maintenance runs.
- Before cutting a new release.
- When the user asks to "update deps", "maintain the package", or "prepare a release".
Key Conventions (apply throughout)
These rules are referenced from individual steps. Read them once.
pocket-icis the only bench runtime — pin it likemoc. mops CLI v3.0.0 removed dfx-replica support entirely (the--replicaflag onmops test/mops benchis gone too). If the package has benchmarks, replica tests, or uses--check-deploy,[toolchain] pocket-icmust be pinned ormops benchfails outright. Unlike thefilesfield, this is not hands-off: if it's missing, add it withmops toolchain use pocket-ic <version>(mops's own error names a concrete version, e.g.15.0.0) the same way[toolchain] mocis actively managed.- Benchmarks should measure optimized Wasm — configure
[optimize]and pinwasm-opt.mops bench(andmops build) only run Binaryen'swasm-optwhen an[optimize]section exists inmops.toml— even an empty[optimize]activates it, at the defaultlevel = "O3"/keep-names = true. Without it, no optimization runs at all and benchmark numbers reflect raw compiler output, not what actually ships in a production canister. Likepocket-ic,[optimize]requires an explicit[toolchain] wasm-optpin or the build fails before compiling. If the package has benchmarks and[optimize]is missing, add both during maintenance (see Step 5) — don't leave this for the user to notice. - Hands-off fields in
mops.toml: never auto-edit[package] filesor auto-add entries to[toolchain]. Report and warn instead. Exceptions:[toolchain] pocket-icand[optimize]/[toolchain] wasm-opt— see the two bullets above; add them when benchmarks require it, same asmoc. - CHANGELOG only lists consumer-visible bumps:
[dependencies]and[requirements]. Never list[toolchain]or[dev-dependencies]. - Documentation gate: if MOPS reports Documentation = 100% (Step 1),
skip Steps 7 and 8 entirely. Otherwise aim for 100% — fill in every
missing
///doc string on a public declaration at minimum. - No
moc --checkin CI. It's a local-only check the agent runs during maintenance; different CI compiler versions could cause spurious failures for consumers. mops addrewritesmops.tomland can silently reset[requirements] mocto"1.0.0". Diff after everymops addand restore the intended value.
Prerequisites
mopsCLI (npm i -g ic-mops)moc(Motoko compiler; pinned via[toolchain] mocinmops.tomland fetched withmops toolchain bin moc— as of mops CLI v3.0.0 there's no dfx-bundled fallback, the pin is mandatory)pocket-ic(bench runtime; pinned via[toolchain] pocket-icwhen the package has benchmarks — see Key Conventions)wasm-opt(Binaryen; only runs when[optimize]is set inmops.toml, and only then requires a[toolchain] wasm-optpin — see Key Conventions)dfx(DFINITY SDK; optional — only needed for building canisters or examples, not formops bench)node>= 22 /npm. Verify withnode --version.gitprettierandprettier-plugin-motoko— install locally before running prettier;npx -yalone does not auto-resolve the plugin in a clean checkout.
Workflow
Work through every step in order. If a step fails, fix it before moving on.
Step 0 — Create a maintenance branch
git checkout -b chore/dependency-bump-$(date +%Y-%m-%d)
Also snapshot the untracked-file list now so you can tell scratch files from your own additions in Step 11:
git status --short > /tmp/baseline.txt
Step 1 — Verify Package Quality on MOPS
- Open
mops.toml. If it has no[package]section (only[canister], or neither), it's not a published package — skip to Step 2. - Otherwise visit
https://mops.one/<package-name>and review the Package Quality section (Documentation, License, Repository, Tests, Benchmarks).mops.oneis a SPA —curl/WebFetchsee only an empty shell. If you can't render it, derive signals locally: runmo-docand grep for(no description); verify[package] licenseand[package] repositoryinmops.toml; confirmmops testandmops benchsucceed. Ask the user to confirm anything you can't verify programmatically.
- For any metric that isn't "yes" / "100%":
- Documentation < 100%: run the
motoko-doc-stringsskill in Steps 7–8. If already 100%, skip Steps 7–8. - Missing License/Repository: set them in
mops.toml[package]. - Tests/Benchmarks: handled in Steps 4–5; ensure CI in Step 9c.
- Documentation < 100%: run the
- Raise a warning to the user for anything that can't be auto-fixed.
Step 2 — Discover outdated dependencies
mops outdated
# Fallback: mops search <package-name>
Record packages needing upgrades.
moc policy: upgrade [toolchain] moc to the latest; set
[requirements] moc to the absolute minimum that still works
(determined iteratively in Step 3) — do not blindly align it with
the toolchain or with what dependencies declare.
Step 3 — Upgrade dependencies in mops.toml
Update versions for every outdated entry in [dependencies] and
[dev-dependencies].
Determining [requirements] moc:
- Initial version — the max
mocrequired by your regular[dependencies], excludingcore:- List
[dependencies]from your rootmops.toml(skipcore). - For each, read
.mops/<name>@<version>/mops.tomland extract[requirements] moc(skip if absent). - Take the max. If lower than your current
[requirements] moc, keep the current value instead.
- List
- Check
core— aftermops install, read.mops/core@<version>/mops.toml[requirements] moc. - Iterative validation (only if
core's requirement > initial): You MUST test EVERY intermediatemocversion sequentially — do not skip (e.g., from 1.0.0 → 1.4.0; test 1.1.0, 1.2.0, 1.3.0…). For each candidateX:- Set
[toolchain] moc = "X". - Run
mops test(if tests exist). - Run
mops bench(if benchmarks exist). - Build examples (Motoko canisters only — ignore asset/Rust):
EXAMPLES_DIR="" if [ -d "examples" ]; then EXAMPLES_DIR="examples" elif [ -d "example" ]; then EXAMPLES_DIR="example"; fi if [ -n "$EXAMPLES_DIR" ]; then cd "$EXAMPLES_DIR" mops install if [ -f "icp.yaml" ] && command -v icp >/dev/null; then icp build --all elif [ -f "dfx.json" ]; then dfx build; fi cd .. fi - If all pass, set
[requirements] moc = "X"and stop.
- Set
- Failure: if even
core's required version fails, revert[requirements] mocto the initial value and warn the user. - Final sync: restore
[toolchain] mocto the latest version.
Then install:
mops install
Verify mops install succeeded without resolution errors and that
mops.lock updated. Never hand-edit the lock file.
Step 3a — Sync nested mops.toml files
Find every nested manifest (examples, sub-canisters, etc.):
find . -name mops.toml -not -path "./node_modules/*" -not -path "./.mops/*"
For each nested mops.toml:
- Third-party
[dependencies]versions should generally match the root (examples may add extras, but shared deps should align). [toolchain] mocmust match the root and exist in the fork used by the CIsetup-mopsaction.- The package being maintained must reference itself by its real
package name via a relative local path
(e.g.
self-package-name = "../"), with a comment showing the latest published version for easy manual swap by consumers. - Replace relative source imports in example
.mofiles:import "../../src/Main"→import "mo:self-package-name/Main". This ensures examples are copy-paste ready.
Example examples/mops.toml:
[dependencies]
core = "2.5.0"
self-package-name = "../"
# Replace with:
# self-package-name = "0.0.3"
[toolchain]
moc = "1.6.0"
Step 3b — Audit repo hygiene files
.gitignore— create if missing; ensure it contains:
Personal/editor files (.mops/ .icp/ mops.lock # for libraries only — mops install reminds you node_modules/ package.json package-lock.json .dfx/ build/ skills-lock.json .agents.idea,.vscode,.claude,.junie,.copilot,.tmp,*.swp,.DS_Store) belong in the developer's global ignore, not here.package-lock.json— if absent, don't introduce it.package.json— if present, ensure itslicensematchesmops.toml. If absent, don't introduce it (and don't commit one thatnpmmay create).mops.toml[package] files— hands off (see Key Conventions). Report its contents to the user and warn if something looks off (e.g.,examples/exists but isn't listed). Only.mo,.did,.md,.tomlare supported bymops publish— warn if other extensions appear.
Step 3c — Fix compiler warnings
Run the fix-compiler-warnings skill. Only act on warnings/errors
the compiler actually emits — do not invent "improvements".
Capture warnings for Motoko canisters only (ignore asset/Rust):
# DFX projects:
dfx build --check 2>&1 | tee /tmp/dfx_build_output.txt
# MOPS packages:
find src -type f -name "*.mo" -print0 | xargs -0 -n1 $(mops toolchain bin moc) --check $(mops sources) 2>&1 | tee /tmp/moc_check_output.txt
# ICP-CLI projects:
icp build 2>&1 | tee /tmp/icp_build_output.txt
Fix one warning class at a time, re-run the check, and run
mops test / mops bench if relevant to catch regressions.
Step 4 — Run tests
mops test
If tests fail, identify whether an upgraded dependency changed its API, adapt the source, and re-run. Note any non-trivial changes (renames, signature changes) for the CHANGELOG.
Step 5 — Run benchmarks
If the package has benchmarks (a bench/ directory with
*.bench.mo files), before running mops bench, make sure the
bench toolchain is fully configured. mops CLI v3.0.0 requires both of
the following to be pinned explicitly, or mops bench fails outright
(see Key Conventions):
[toolchain] pocket-ic— if missing, add it:mops toolchain use pocket-ic <version> # mops's own error names one, e.g. 15.0.0[optimize]+[toolchain] wasm-opt— if[optimize]is missing frommops.toml, add it now, don't just flag it. An empty section is enough (defaults tolevel = "O3",keep-names = true):
Pin the version the same way as[optimize] [toolchain] wasm-opt = "<version>"pocket-ic:
Withoutmops toolchain use wasm-opt <version> # or: mops toolchain info wasm-opt[optimize],mops benchsilently measures unoptimized Wasm — not what the CHANGELOG's benchmark numbers should represent. If you add[optimize]to a package that didn't have it before, say so in the CHANGELOG (Step 6): the resulting shift in bench numbers is a measurement-methodology change, not a code regression/improvement.
Then run:
mops install # picks up any new [toolchain] pins
mops bench
Apply Step 4-style fixes for compile/run failures.
Environmental failures vs code failures. If mops bench errors
after the Deploying canisters... line — typically:
TrustError: Certificate verification error: "Invalid signature"UND_ERR_SOCKET/fetch failedCould not find the PocketIC binary
— benchmarks compiled fine; this is a pocket-ic/mops agent
compatibility issue, not your bump. In order: kill stale pocket-ic
processes; confirm [toolchain] pocket-ic is pinned and re-fetch the
binary (mops toolchain bin pocket-ic), re-pinning to a newer
version with mops toolchain use pocket-ic <version> if it looks
stale or missing (see Key Conventions — there is no dfx-replica
fallback to drop back to as of mops CLI v3.0.0); as a last resort,
drop the bench job from CI and flag the user. Don't burn the
release chasing this.
Significant regressions: note for the CHANGELOG but don't block the release unless the user asks.
Step 6 — Update the CHANGELOG
Open or create CHANGELOG.md and add a section at the top for the
upcoming version (number finalized in Step 10).
Include (only [dependencies] and [requirements] — see Key
Conventions):
- Dependencies bumped — list each upgraded
[dependencies]entry, old → new. - Breaking / notable changes — source-code adaptations.
- Bug fixes — anything found and fixed during the run.
If no CHANGELOG exists, create one:
# Changelog
## Unreleased
### Changed
- Updated `core` from `2.0.0` to `2.5.0`.
- Bumped `bench` from `1.0.0` to `2.0.1`.
- Updated `[requirements] moc` from `1.2.0` to `1.3.0` (only if changed).
### Fixed
- Adapted `<function>` to new `<dep>` API (renamed `old` → `new`).
Newest version on top. Match an existing CHANGELOG style if present.
Step 7 — Review and improve doc strings
Skip if MOPS Documentation = 100% (see Key Conventions). Otherwise aim for 100%.
Scan every .mo file under src/ for public declarations
(type, func, actor, actor class, let, module). For each:
- Ensure a
///doc string exists directly above. - From a first-time user's perspective: is the purpose clear? Are argument types/units/constraints documented? Trap/error behavior? Examples for non-trivial functions?
- Improve or add as needed.
If the motoko-doc-strings skill is installed, follow its checklist.
Step 8 — Review README and other Markdown
Skip if MOPS Documentation = 100%.
For README.md and every other .md:
- Check factual accuracy (versions, API names, examples).
- Fix outdated information; improve clarity, grammar, completeness.
- Add sections for new API surface from upgraded deps.
- Ensure
README.mddocuments the formatter command, e.g.npx -y prettier --plugin prettier-plugin-motoko --write '**/*.{mo,json,md}'(add a "Development" or "Formatting" section if missing).
Step 9 — Formatting
9a — Prettier configuration
If .prettierrc is missing, or exists but is trivial (e.g. only
tabWidth), write:
{
"plugins": ["prettier-plugin-motoko"],
"bracketSpacing": true,
"printWidth": 80,
"semi": true,
"tabWidth": 2,
"trailingComma": "es5",
"useTabs": false
}
9b — Format the code
prettier's --plugin flag does not auto-resolve packages. Install
the plugin locally first (a bare npx -y prettier --plugin prettier-plugin-motoko ... fails with Cannot find package 'prettier-plugin-motoko' in a clean checkout):
npm install --no-save prettier prettier-plugin-motoko
npx prettier --plugin prettier-plugin-motoko --write '**/*.{mo,json,md}'
9c — Verify or add CI workflow
If the motoko-github-ci-workflow skill is installed, follow its
instructions instead of this section.
Otherwise create/update .github/workflows/ci.yml with tests +
formatting. If the package has benchmarks, [toolchain] pocket-ic must be pinned (per Key Conventions) and installed in CI
with mops toolchain bin pocket-ic before mops bench — PocketIC is
the only bench runtime as of mops CLI v3.0.0. mops bench starts and
stops its own PocketIC instance — never add a manual dfx start --background step; remove any you find. If mops.toml has an
[optimize] section (it should, per Key Conventions — added in
Step 5 if missing), also install wasm-opt in CI via mops toolchain bin wasm-opt before mops bench.
name: CI
on:
push:
branches: [main, master]
pull_request:
types: [opened, synchronize, reopened, ready_for_review]
jobs:
test:
name: Tests and Benchmarks
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
with:
node-version: latest
- uses: caffeinelabs/setup-mops@v1
- run: mops install
- run: mops test # Omit if no tests
# Benchmarks: pocket-ic is the only bench runtime (mops CLI
# v3.0.0 removed dfx-replica support). Requires [toolchain]
# pocket-ic to be pinned in mops.toml. Omit both steps if there
# are no benchmarks.
- name: Make sure pocket-ic is installed
run: mops toolchain bin pocket-ic
# Only if mops.toml has an [optimize] section (see Key
# Conventions / Step 5) — otherwise omit this step too.
- name: Make sure wasm-opt is installed
run: mops toolchain bin wasm-opt
- run: mops bench
fmt:
name: Formatting Check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
with:
node-version: latest
- name: Prettier Check
run: |
npm install prettier prettier-plugin-motoko --no-save
npx -y prettier --plugin prettier-plugin-motoko --check '**/*.{mo,json,md}'
Per Key Conventions, do not add a moc --check step to CI.
Step 10 — Bump the version
In root
mops.toml, increment the version. Default = patch (1.2.3→1.2.4). Bump minor (and reset patch) if any of:[requirements] mocwas raised (consumers need a newer compiler);- a
[dependencies]package crossed a major and this package re-exports types/functions from it; - source signatures or runtime semantics consumers might rely on changed.
For
0.x.y, shift the rule one segment right.[package] name = "my-package" version = "1.2.4" # was 1.2.3Update the CHANGELOG
Unreleasedheader to the new number:## 1.2.4.Update self-dependency comments in every nested
mops.toml(from Step 3a) to the new version:self-package-name = "../" # Replace with: # self-package-name = "1.2.4"
Step 11 — Commit and push
Audit untracked files first. Compare git status --short against
/tmp/baseline.txt (Step 0). Files that predate your work
(*.debug.bench.mo, roxy.txt, ad-hoc experiments) are scratch —
leave unstaged, .gitignore them, or delete. Never git add -A
blindly. Ask the user if unsure.
Stage changes. If package.json / package-lock.json didn't exist
at the start but npm install created them, do not commit them.
git add -u
git add .gitignore CHANGELOG.md .prettierrc .github/workflows/*.yml 2>/dev/null || true
git status
git commit -m "chore: bump dependencies and prepare v<NEW_VERSION>"
Inform the user:
Branch: chore/dependency-bump-YYYY-MM-DD
Ready for review. Run `git push -u origin HEAD` to push.
Common Pitfalls
- Major-version dep bumps. If a MOPS dep jumps a major
(
1.x→2.x), read its CHANGELOG — the API likely changed. Flag non-trivial migrations to the user. [requirements] mocexhaustive search. Test EVERY intermediate version (Step 3); never skip. Prefer the lowest version that passes all checks, even below whatcoredeclares, to maximize consumer compatibility.- Lock file drift. Always run
mops installafter editingmops.toml. Never hand-editmops.lock. - CHANGELOG ordering. Newest at the top.
- Benchmarking without
[optimize]. If a package has benchmarks but no[optimize]section,mops benchsilently measures unoptimized Wasm (see Key Conventions / Step 5). Don't skip adding it just becausemops bench"already works" without it — working and representative are different things.
Verify It Works
mops install
mops test
mops bench
All must succeed with no errors.