Validate Perps Multiproject
Validate one perps change across multiple local MetaMask repo checkouts.
Canonical direction: a CLIENT validates a CORE controller change. The
@metamask/perps-controller package lives in Core; Mobile and Extension consume
it. The owner is wherever the controller change is; the targets are the clients
that must not break. (Client↔client parity is a secondary mode — see below.)
Deterministic layer — use scripts/perps-validate.sh
Do not re-derive the per-folder commands each run. scripts/perps-validate.sh
encodes them and is portable across machines and Node managers. Run it in order:
SC="$(dirname "$0")/scripts/perps-validate.sh" # or the skill's scripts/ path
perps-validate.sh doctor <core-dir> <client-dir> # sanity: branches, yalc, links
perps-validate.sh prestate <client-dir> [client-dir...] # snapshot BEFORE touching anything
perps-validate.sh build <core-dir> [--full] # build pkg; freshness gate
perps-validate.sh push <core-dir> <client-dir> [...] # yalc publish + push into clients
perps-validate.sh verify <client-dir> # version + new symbols landed?
# -> then run the client's own proof (type-check + the affected tests)
perps-validate.sh restore <client-dir> [client-dir...] # pre-state-aware restore
The script makes no assumption about the Node manager and resolves yalc
robustly (see "yalc resolution" below). Everything else in this doc explains the
why so you can adapt when a step deviates.
Defaults
- Owner checkout: current repo/cwd unless a path is provided.
- Folder layout: assume Core/controller, Mobile, and Extension are sibling folders under one workspace.
- Targets:
- Core/controller owner -> validate in Mobile and Extension when available;
- Mobile owner -> validate parity in Extension;
- Extension owner -> validate parity in Mobile.
- Transport:
yalcfor@metamask/perps-controller; none for client parity. - Proof: smallest meaningful proof. For a client validating a Core change, the high-value proof is the client type-check (catches removed/renamed/ changed exports) plus the client tests that exercise the changed surface. Add recipe/e2e or real UI flow only when runtime behavior changes.
- Target edits: forbidden unless explicitly allowed.
- Cleanup: required, and pre-state aware — a client that was already on a yalc link must be restored to that exact link, not wiped to a registry baseline.
Step 0 — resolve folders or ask
Do not guess missing folders. Discover likely local checkouts, then ask only for unresolved choices.
HERE=$(git rev-parse --show-toplevel 2>/dev/null || pwd)
PARENT=$(dirname "$HERE")
# Prefer sibling folders in the same workspace.
for name in core metamask-core controller metamask-mobile mobile metamask-extension extension; do
[ -d "$PARENT/$name" ] && echo "$PARENT/$name"
done
# Optional fallback if repos are not siblings.
ROOT="${METAMASK_REPOS_DIR:-$HOME/dev/metamask}"
find "$ROOT" -maxdepth 1 -type d \
\( -name 'core*' -o -name 'controller*' -o -name 'metamask-mobile*' -o -name 'mobile*' -o -name 'metamask-extension*' -o -name 'extension*' \) -print 2>/dev/null
Note: a workspace may hold many numbered clones (core-1..core-6,
metamask-mobile-1..6). Confirm WHICH core holds the change and WHICH client to
validate — do not assume the first match.
If needed, ask one concise question using the runtime's interactive question tool when available:
I need the validation folders:
- Owner checkout (Core with the change): <default/candidates>
- Validation target client(s): <default/candidates>
- Proof level: transport-only | type/import | recipe/e2e | real UI flow
- May validation targets be edited? default no
Echo the resolved contract before changing anything.
Worker context to inject
## Perps multiproject validation
Owner: <core|mobile|extension> `<path>` on `<branch>`
Targets:
- <project> `<path>` — <read-only|editable> — purpose: <integration/parity>
Rules:
1. Capture `git status --short --branch` in every checkout first (`prestate`).
2. Edit only the owner checkout unless a target is explicitly editable.
3. For Core package validation, build first, then publish via yalc; never publish stale `dist/`.
4. Prove the built `dist/` actually carries the change (version bump + new symbols) before publishing.
5. Run the smallest proof that reaches the changed perps behavior.
6. Restore each client to its snapshot; a pre-existing yalc link must come back byte-for-byte.
Core -> clients via yalc
scripts/perps-validate.sh runs all of this. The manual equivalents below are
for when you must deviate.
yalc resolution (do not assume asdf/nvm/brew)
yalc is the single most fragile dependency across machines. A version-manager
shim can exit 0 yet do nothing — e.g. asdf prints No version is set for command yalc and returns success, so yalc publish silently no-ops. Never trust
the exit code alone: a working yalc prints a semver to stdout.
Resolution order (what the script does):
YALC_BINenv override, if set.command yalc --version— accept only if it prints a semver.- Otherwise locate yalc's own
yalc.jsand run it through any workingnode. Searched layouts:$(npm root -g)/yalc,~/.asdf/installs/nodejs/*/lib/...,~/.nvm/versions/node/*/lib/...,~/.volta/...,/opt/homebrew/lib/...,/usr/local/lib/....
If all else fails: npm i -g yalc, or pass an explicit
YALC_BIN="node /abs/path/to/yalc/src/yalc.js".
# Pre-state for every checkout (the script's `prestate` does this + a byte-for-byte
# backup of any existing .yalc link).
for repo in /path/to/core /path/to/client; do
echo "--- $repo"; git -C "$repo" status --short --branch
(cd "$repo" && printf 'node=%s yarn=%s\n' "$(node -v)" "$(yarn -v)")
done
Building the package — expect a full monorepo build
The perps-controller package cannot build standalone in a fresh Core checkout.
Its tsconfig.build.json uses project references, so yarn workspace @metamask/perps-controller build fails with TS6305: Output file ... has not been built from source file for every dependency package whose dist/ is missing
(network-controller, transaction-controller, controller-utils,
profile-sync-controller, remote-feature-flag-controller, messenger, …). This is
the normal state of a fresh checkout, not a perps-controller bug.
The supported fix is a full monorepo build first:
cd /path/to/core
nice -n 10 yarn build # builds all referenced dists in dependency order
This takes several minutes (40+ packages) but is clean and leaves the checkout
buildable for later runs. Do not use yarn workspaces foreach --from @metamask/perps-controller -R ... — it can fail on the
account-tree/multichain/perps/snap cycle and delete dist/ on the way out.
perps-validate.sh build <core> --full wraps nice -n 10 yarn build; the
plain build form detects TS6305 and tells you to re-run with --full.
Freshness gate
yalc publish success is meaningful only when the current run produced a
fresh packages/perps-controller/dist, and that dist carries the change. Verify
before trusting it:
D=/path/to/core/packages/perps-controller
node -p "require('$D/package.json').version" # expected bump
ls "$D"/dist/index.cjs # dist exists
grep -l "<new export symbol>" "$D"/dist/index.d.cts # new surface present
Publish + push (advancing an existing link)
A client may already be on a yalc link (e.g. mid-feature dev). In that case
yalc add is wrong — use yalc update to advance the existing link in place.
perps-validate.sh push picks the right one automatically.
cd /path/to/core/packages/perps-controller && yalc publish --private --push
# `--push` propagates to every linked project. WATCH THE OUTPUT: it will touch
# *other* repos that link the same package (e.g. a sibling clone), not just your
# target. That is a side effect to note, not a failure.
Run the proof, then restore
Client proof for "controller won't break":
cd /path/to/client
# 1. type-check — the strongest signal for export/type regressions.
NODE_OPTIONS='--max-old-space-size=8192' npx tsc --noEmit --incremental \
--tsBuildInfoFile .tsbuildinfo --project ./tsconfig.json
# 2. the client tests that exercise the changed surface (NOT the whole suite,
# NOT --findRelatedTests).
yarn jest <files that import the changed symbols> --no-coverage
Cleanup — pre-state aware (critical)
The old "rm -rf .yalc yalc.lock && git checkout package.json yarn.lock" recipe is
destructive when the client was already on a yalc link before you started: it
wipes the user's active dev setup. Restore to the snapshot taken by prestate:
- Client was already yalc-linked → restore the backed-up
.yalc/<pkg>byte-for-byte, restoreyalc.lock/package.json, re-runyarn install --mode=skip-buildif node_modules must match. - Client had no prior link (registry baseline) →
yalc remove <pkg>,git checkout -- package.json yarn.lock,rm -rf .yalc yalc.lock.
perps-validate.sh restore <client> does the right one based on the recorded
mode. If you advanced a client deliberately and the user wants to keep the new
version, say so explicitly and keep the snapshot for a later restore.
Client parity
For Mobile <-> Extension checks:
- Load relevant installed perps knowledge from
knowledge/:mobile-extension-map,screens,shared-package-analysis, architecture docs as needed. - Find the equivalent screen/hook/flow in the other client.
- Validate with real flow evidence; do not inject UI state.
- Report semantic differences separately from regressions.
Stop conditions
Stop and report when the owner package cannot build (after --full), a target
checkout has unexpected dirt, cleanup would delete user work, target source edits
are needed but not allowed, or required device/browser/credential context is
missing.
Final answer
Validated <change> against <targets>.
- Owner: <path>@<branch>, status <clean/expected dirty>
- Targets: <path/status>
- Transport: <yalc package/version or none> (built via full monorepo build / package build)
- Proof: <type-check + tests/recipes> => <pass/fail>
- Artifacts: <paths>
- Cleanup: <restored to snapshot | left on new version by request + snapshot kept>
- Follow-ups/blockers: <if any>