@deeeed/metamask-harness 0.17.5 → 0.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (169) hide show
  1. package/CHANGELOG.md +1059 -0
  2. package/README.md +91 -140
  3. package/adapters/core/inject.sh +1 -6
  4. package/adapters/extension/console-tail.mjs +55 -24
  5. package/adapters/extension/ensure-browser.sh +6 -1
  6. package/adapters/extension/inject.mjs +7 -13
  7. package/adapters/extension/launch-browser.cjs +7 -1
  8. package/adapters/extension/launch-webpack.cjs +83 -0
  9. package/adapters/extension/launch.sh +8 -11
  10. package/adapters/extension/lib/chrome-args.cjs +8 -1
  11. package/adapters/extension/lib/macos-focus.cjs +32 -0
  12. package/adapters/extension/live.sh +12 -21
  13. package/adapters/extension/reattach.sh +1 -0
  14. package/adapters/extension/seed-fixture.sh +4 -12
  15. package/adapters/extension/sidepanel-toggle.sh +4 -1
  16. package/adapters/extension/stamp-runtime-title.cjs +58 -0
  17. package/adapters/extension/start-watch.sh +17 -4
  18. package/adapters/extension/stop-viewers.sh +1 -1
  19. package/adapters/extension/sync-webpack-dist.cjs +107 -0
  20. package/adapters/extension/verify.sh +2 -2
  21. package/adapters/manifest.json +53 -37
  22. package/adapters/mobile/bridge-runtime/cdp-bridge.cjs +16 -0
  23. package/adapters/mobile/bridge-runtime/console-forwarder.cjs +1 -1
  24. package/adapters/mobile/bridge-runtime/lib/match-bridge-target.cjs +3 -1
  25. package/adapters/mobile/bridge-runtime/lib/target-discovery.cjs +2 -3
  26. package/adapters/mobile/cleanup.sh +16 -176
  27. package/adapters/mobile/inject.sh +91 -671
  28. package/adapters/mobile/launch-console-forwarder.cjs +39 -0
  29. package/adapters/mobile/open-device.sh +45 -7
  30. package/adapters/mobile/start-console-forwarder.sh +70 -0
  31. package/adapters/mobile/start-metro.sh +0 -41
  32. package/adapters/mobile/verify.sh +34 -83
  33. package/adapters/mobile/wait-for-bridge.sh +22 -8
  34. package/adapters/shared/harness-source-fingerprint.mjs +14 -12
  35. package/adapters/shared/resolve-slot-ports-core.mjs +17 -6
  36. package/adapters/shared/resolve-slot-ports.sh +17 -5
  37. package/adapters/shared/update-check-worker.mjs +43 -0
  38. package/bin/mm-harness +21 -2
  39. package/dist/adapters/core/surface.js +4 -1
  40. package/dist/adapters/extension/console-capture.js +95 -0
  41. package/dist/adapters/extension/product-config.js +110 -0
  42. package/dist/adapters/extension/runtime-decision.js +20 -2
  43. package/dist/adapters/extension/surface.js +22 -3
  44. package/dist/adapters/harness-freshness.js +49 -0
  45. package/dist/adapters/mobile/perps-env.js +43 -3
  46. package/dist/adapters/mobile/prepare.js +39 -7
  47. package/dist/adapters/mobile/surface.js +5 -2
  48. package/dist/adapters/resolve-slot-ports.js +2 -2
  49. package/dist/adapters/slot-ports.js +13 -16
  50. package/dist/adapters.js +56 -16
  51. package/dist/checkout-lock.js +27 -2
  52. package/dist/cli-color.js +19 -0
  53. package/dist/cli-commands.js +1 -1
  54. package/dist/cli.js +7 -18
  55. package/dist/command-contract.js +462 -0
  56. package/dist/command-journal.js +263 -0
  57. package/dist/commands/call.js +252 -57
  58. package/dist/commands/check.js +10 -4
  59. package/dist/commands/completion-candidates.js +17 -22
  60. package/dist/commands/device-target.js +27 -12
  61. package/dist/commands/doctor.js +106 -25
  62. package/dist/commands/fixtures.js +142 -35
  63. package/dist/commands/last.js +60 -0
  64. package/dist/commands/launch/extension.js +40 -17
  65. package/dist/commands/launch/index.js +207 -63
  66. package/dist/commands/launch/mobile.js +2 -0
  67. package/dist/commands/list-executables.js +187 -29
  68. package/dist/commands/logs.js +8 -6
  69. package/dist/commands/manifest.js +286 -35
  70. package/dist/commands/parse-args.js +68 -4
  71. package/dist/commands/provision.js +10 -4
  72. package/dist/commands/run-engine.js +772 -264
  73. package/dist/commands/run-report.js +12 -3
  74. package/dist/commands/run.js +378 -85
  75. package/dist/commands/shared.js +76 -4
  76. package/dist/commands/status-probe.js +4 -1
  77. package/dist/commands/status.js +2 -1
  78. package/dist/commands/stop.js +7 -2
  79. package/dist/commands/update.js +16 -28
  80. package/dist/completions-cache.js +1 -1
  81. package/dist/doctor.js +57 -10
  82. package/dist/harness.js +47 -137
  83. package/dist/heal-bounds.js +7 -2
  84. package/dist/json-stream.js +57 -0
  85. package/dist/live-adapter-contract.js +138 -32
  86. package/dist/manifest.js +161 -1
  87. package/dist/mm-harness-cli.js +146 -73
  88. package/dist/paths.js +2 -5
  89. package/dist/recipe-security.js +178 -0
  90. package/dist/run-diagnostics.js +261 -0
  91. package/dist/run-recording.js +1 -1
  92. package/dist/runner.js +140 -9
  93. package/docs/CONTRIBUTING.md +136 -0
  94. package/docs/QA.md +184 -0
  95. package/docs/RECIPES.md +122 -0
  96. package/docs/SECURITY.md +88 -0
  97. package/library/README.md +7 -3
  98. package/library/actions/core/perps/_controller.mjs +43 -56
  99. package/library/actions/core/perps/assert_orders.mjs +6 -7
  100. package/library/actions/core/perps/assert_positions.mjs +6 -7
  101. package/library/actions/core/perps/close_orders.mjs +2 -0
  102. package/library/actions/core/perps/close_positions.mjs +2 -0
  103. package/library/actions/core/perps/ensure_orders.mjs +4 -2
  104. package/library/actions/core/perps/ensure_positions.mjs +4 -2
  105. package/library/actions/core/perps/place_order.mjs +7 -3
  106. package/library/actions/core/perps/read_account.mjs +2 -2
  107. package/library/actions/core/perps/read_orders.mjs +2 -1
  108. package/library/actions/core/perps/read_positions.mjs +2 -1
  109. package/library/actions/core/wallet/list_accounts.mjs +95 -0
  110. package/library/actions/extension/perps/assert_orders.mjs +2 -1
  111. package/library/actions/extension/perps/assert_positions.mjs +2 -1
  112. package/library/actions/extension/perps/perps.mjs +43 -14
  113. package/library/actions/extension/platform/cdp.mjs +1 -0
  114. package/library/actions/extension/wallet/list_accounts.mjs +41 -0
  115. package/library/actions/mobile/perps/assert_orders.mjs +2 -1
  116. package/library/actions/mobile/perps/assert_positions.mjs +2 -1
  117. package/library/actions/mobile/perps/perps.mjs +40 -12
  118. package/library/actions/mobile/wallet/list_accounts.mjs +37 -0
  119. package/library/library.json +1 -1
  120. package/library/manifests/core.action-manifest.json +1214 -396
  121. package/library/manifests/extension.action-manifest.json +1635 -728
  122. package/library/manifests/mobile.action-manifest.json +1740 -727
  123. package/library/recipes/app/lifecycle.android-smoke.mobile.recipe.json +63 -81
  124. package/library/recipes/perps/clean-market-testnet.core.recipe.json +44 -0
  125. package/library/recipes/perps/clean-market-testnet.recipe.json +49 -0
  126. package/library/recipes/perps/lifecycle.recipe.json +136 -180
  127. package/library/recipes/perps/order-lifecycle.core.recipe.json +71 -67
  128. package/library/recipes/perps/performance.background-resume.mobile.recipe.json +51 -67
  129. package/library/recipes/perps/performance.cold-start.mobile.recipe.json +51 -67
  130. package/library/recipes/perps/performance.mobile.recipe.json +37 -51
  131. package/library/recipes/perps/performance.warm-start.mobile.recipe.json +44 -59
  132. package/library/recipes/perps/read-markets.core.recipe.json +29 -31
  133. package/library/recipes/perps/smoke.core.recipe.json +29 -32
  134. package/library/recipes/perps/smoke.extension.recipe.json +41 -44
  135. package/library/recipes/perps/smoke.mobile.recipe.json +42 -44
  136. package/library/recipes/perps/trading-lifecycle.core.recipe.json +69 -65
  137. package/library/recipes/runner/action-validation.extension.recipe.json +312 -398
  138. package/library/recipes/runner/action-validation.mobile.recipe.json +316 -402
  139. package/library/recipes/runner/smoke.core.recipe.json +25 -0
  140. package/library/recipes/runner/smoke.extension.recipe.json +23 -24
  141. package/library/recipes/runner/smoke.mobile.recipe.json +23 -24
  142. package/library/recipes/wallet/smoke.extension.recipe.json +40 -0
  143. package/library/recipes/wallet/smoke.mobile.recipe.json +40 -0
  144. package/package.json +12 -5
  145. package/scripts/completions.sh +8 -11
  146. package/scripts/validate-human-outcomes.mjs +169 -0
  147. package/adapters/mobile/overlay/app/dev-tools/AgenticService/AgentStepHud.tsx.patch +0 -185
  148. package/adapters/mobile/overlay/app/dev-tools/AgenticService/AgenticService.ts.patch +0 -1662
  149. package/adapters/mobile/overlay/compat/README.md +0 -28
  150. package/adapters/mobile/overlay/compat/rn81-message-event-source.patch +0 -42
  151. package/adapters/shared/resolve-farmslot-ports-core.mjs +0 -3
  152. package/adapters/shared/resolve-farmslot-ports.mjs +0 -5
  153. package/adapters/shared/resolve-farmslot-ports.sh +0 -7
  154. package/dist/adapters/extension/harness-freshness.js +0 -39
  155. package/dist/commands/flows.js +0 -62
  156. package/docs/ADAPTER-SURFACE.md +0 -119
  157. package/docs/CHEATSHEET.md +0 -61
  158. package/docs/CLI-SPEC.md +0 -1098
  159. package/docs/CODE-MAP.md +0 -62
  160. package/docs/DEBUG-HANDOVER.md +0 -36
  161. package/docs/MENTAL-MODEL.md +0 -295
  162. package/docs/UX-PRINCIPLES.md +0 -64
  163. package/docs/architecture.md +0 -398
  164. package/docs/live-adapter-contract.md +0 -188
  165. package/docs/package-boundaries.md +0 -47
  166. package/docs/perps-flow-catalog.md +0 -235
  167. package/docs/recipe-libraries.md +0 -301
  168. package/docs/runtime-file-conventions.md +0 -36
  169. package/library/flows/perps.flows.json +0 -64
@@ -1,235 +0,0 @@
1
- # Perps Flow Catalog
2
-
3
- MetaMask Perps recipes use a small set of parameterized primitives plus higher-level `ensure_*` wrappers. Do not add one action per scenario. A new Perps capability should first try to extend selector params or flow params.
4
-
5
- ## Layers
6
-
7
- | Layer | Examples | Responsibility |
8
- |---|---|---|
9
- | Read primitives | `metamask.perps.read_positions`, `metamask.perps.read_orders` | Return redacted live state for a selected subset. |
10
- | Bulk mutation primitives | `metamask.perps.close_positions`, `metamask.perps.close_orders`, `metamask.perps.place_order` | Perform one real product operation over a parameterized selection. |
11
- | Assertion wrappers | `metamask.perps.assert_positions`, `metamask.perps.assert_orders` | Read live state and fail when selected state does not match expectation. |
12
- | Ensure wrappers | `metamask.perps.ensure_positions`, `metamask.perps.ensure_orders` | Compose read/mutate/assert to create idempotent start states. |
13
- | Domain setup/teardown | `metamask.perps.start_state`, `metamask.perps.teardown_state` | Compose wallet/provider/network/page/position/order setup before a proof window. |
14
-
15
- ## Selection contract
16
-
17
- Position/order operations accept the same selector shape:
18
-
19
- ```json
20
- {
21
- "action": "metamask.perps.close_positions",
22
- "mode": "matching",
23
- "markets": ["BTC", "ETH"],
24
- "side": "long",
25
- "timeout_ms": 30000
26
- }
27
- ```
28
-
29
- - `mode: "matching"` selects `market`/`symbol` or `markets`/`symbols`.
30
- - `mode: "all"` selects every live position/order returned by the product.
31
- - `side` narrows selection when the product returns side information.
32
- - `selector` can carry the same fields when a nested shape is easier for flow composition.
33
-
34
- ## Examples
35
-
36
- Close every live Perps position before a proof window:
37
-
38
- ```json
39
- { "action": "metamask.perps.close_positions", "mode": "all" }
40
- ```
41
-
42
- Cancel only BTC open orders:
43
-
44
- ```json
45
- { "action": "metamask.perps.close_orders", "market": "BTC" }
46
- ```
47
-
48
- Higher-level clean start state:
49
-
50
- ```json
51
- {
52
- "action": "metamask.perps.ensure_positions",
53
- "state": "none",
54
- "mode": "all"
55
- }
56
- ```
57
-
58
- Provider/network setup belongs in a configurable start-state contract such as `metamask.perps.start_state`, not in duplicate primitive actions:
59
-
60
- ```json
61
- {
62
- "flow": "metamask.perps.start_state",
63
- "params": {
64
- "provider": "hyperliquid",
65
- "network": "testnet",
66
- "page": "market",
67
- "market": "BTC",
68
- "positions": { "state": "none", "mode": "all" },
69
- "orders": { "state": "none", "mode": "all" }
70
- }
71
- }
72
- ```
73
-
74
- ## Position update operations
75
-
76
- Perps needs more than open/close. When TP/SL, margin, or leverage update coverage is added, prefer one parameterized position-update primitive instead of one adapter file per feature. The primitive should accept a position selector, a small set of update operations, and explicit assertions over the resulting position/order state.
77
-
78
- That future primitive should compose controller-backed product APIs when that is the supported app path. It must not mutate React/Redux/MobX/DOM/local storage directly.
79
-
80
- ## Direct controller usage
81
-
82
- When a recipe genuinely needs direct controller access and there is no reusable UI-equivalent flow, the agent should call the product controller directly through the platform bridge/CDP capability documented for that project.
83
-
84
- The runner should help by documenting discoverable controller capabilities, not by hiding them behind another action vocabulary. For each direct controller capability, document:
85
-
86
- - controller/API path;
87
- - supported params;
88
- - whether it changes state;
89
- - required preconditions;
90
- - expected postcondition assertion;
91
- - redaction rules for trace output.
92
-
93
- Example documentation shape:
94
-
95
- ```json
96
- {
97
- "capability": "PerpsController.setTpSl",
98
- "access": "direct-controller",
99
- "params": {
100
- "market": "BTC",
101
- "takeProfit": { "price": "72000" },
102
- "stopLoss": { "price": "65000" }
103
- },
104
- "postcondition": {
105
- "action": "metamask.perps.assert_orders",
106
- "market": "BTC",
107
- "state": "present"
108
- }
109
- }
110
- ```
111
-
112
- State-changing direct controller calls must still be followed by a read/assert step or by an `ensure_*` wrapper that proves the final state.
113
-
114
- ## Limiting live-adapter file count
115
-
116
- Perps should converge to one domain dispatcher per platform, not one new file for every controller operation. The runner can still expose many manifest actions, but implementation should route through a small domain module:
117
-
118
- ```text
119
- library/actions/mobile/perps/perps.mjs # shared Mobile Perps operation catalog
120
- library/actions/extension/perps/perps.mjs # shared Extension Perps operation catalog
121
- ```
122
-
123
- Runner resolution supports a domain dispatcher fallback such as `library/actions/<platform>/perps/perps.mjs` when an action-specific file is absent. That keeps discoverability in the manifest while avoiding a growing pile of tiny wrapper files.
124
-
125
- ## Rule of thumb for adding adapter files
126
-
127
- Add a named adapter/flow when it represents a reusable product workflow that normally has a UI path and would otherwise waste proof-video time on repetitive setup. Examples: unlock wallet, navigate to Perps, ensure provider/network, open a market, create a baseline position, close selected positions, cancel selected orders, or prepare an order form.
128
-
129
- Do not add a new adapter file only because a controller exposes another method. For direct controller operations, document the real controller capability and params so the agent can call the product API directly when appropriate.
130
-
131
- | Need | Preferred shape | Why |
132
- |---|---|---|
133
- | Faster setup for a visual proof with a matching UI/user workflow | named flow or semantic adapter, e.g. `ensure_positions`, `close_positions` | Keeps proof videos focused while still using supported app/API paths. |
134
- | Direct product capability with many variants | documented direct controller capability + required postcondition | Avoids one file per controller method and avoids a useless wrapper vocabulary. |
135
- | Ticket-specific visual claim | task-local recipe assertion/evidence | Avoids polluting the domain API with one-off checks. |
136
- | Repeated domain baseline across teams | catalog flow such as `start_state` | Creates a stable starting contract for many recipes. |
137
-
138
- Direct controller capabilities must still be discoverable in docs/manifest metadata. The metadata should describe the real controller/API path, required params, examples, postconditions, and whether the operation is state-changing. The agent should be able to infer valid direct calls without reverse-engineering app internals first.
139
-
140
- ## Preconditions vs prestate
141
-
142
- Existing Mobile recipes use pre-conditions such as `wallet.unlocked`, `perps.feature_enabled`, `perps.ready_to_trade`, `perps.sufficient_balance`, `perps.open_position`, `perps.open_position_tpsl`, `perps.open_limit_order`, `perps.not_in_watchlist`, and `perps.trading_flag`.
143
-
144
- Recipe v1 should keep the same concept, but split it into two different responsibilities:
145
-
146
- | Concept | Mutates state? | Purpose | Examples |
147
- |---|---:|---|---|
148
- | `preconditions` / `requires` | No | Fast fail with actionable reason when the target cannot support the recipe. | `wallet.unlocked`, `perps.feature_enabled`, `perps.trading_flag` |
149
- | `prestate` / `startState` | Yes, when needed | Converge the app to a configurable baseline before the proof window. | unlock, select account, choose network/provider, close positions/orders, open market |
150
-
151
- A precondition should not repair state. It answers “is this environment eligible?” A prestate answers “make the environment look like this before the proof starts.”
152
-
153
- ## Recommended Perps base prestate
154
-
155
- Most Perps recipes should inherit from a configurable base prestate instead of repeating setup inline:
156
-
157
- ```json
158
- {
159
- "flow": "metamask.perps.start_state",
160
- "params": {
161
- "wallet": { "state": "unlocked", "account": "default" },
162
- "featureFlags": { "perps": true, "trading": true },
163
- "provider": "hyperliquid",
164
- "network": "testnet",
165
- "readyToTrade": true,
166
- "balance": { "minWithdrawableUsd": "1" },
167
- "page": "market",
168
- "market": "BTC",
169
- "positions": { "state": "none", "mode": "matching", "markets": ["BTC"] },
170
- "orders": { "state": "none", "mode": "matching", "markets": ["BTC"] },
171
- "hud": { "enabled": true, "proofSafe": true }
172
- }
173
- }
174
- ```
175
-
176
- This flow should compose smaller actions/flows:
177
-
178
- 1. `metamask.wallet.ensure_unlocked`
179
- 2. `metamask.wallet.select_account` when requested
180
- 3. provider/network/testnet setup for Hyperliquid or another provider
181
- 4. read-only gates for feature/trading readiness
182
- 5. `metamask.perps.ensure_orders` for order baseline
183
- 6. `metamask.perps.ensure_positions` for position baseline
184
- 7. `ui.navigate` with a manifest-discoverable `page` alias, or raw route/hash fallback
185
- 8. `app.hud` when visual proof should show context and it will not obscure the claim
186
-
187
- ## Base prestate profiles
188
-
189
- The runner should publish named profiles as examples, but keep them parameterized:
190
-
191
- | Profile | Intended use | Default params |
192
- |---|---|---|
193
- | `perps.clean_market_testnet` | Most visual AC proofs from a clean market screen | Hyperliquid testnet, wallet unlocked, ready to trade, selected market, no selected positions/orders |
194
- | `perps.open_position_testnet` | TP/SL, margin, close-position proofs | Hyperliquid testnet, selected market, one open position, optional side/notional/leverage |
195
- | `perps.open_order_testnet` | Limit-order edit/cancel proofs | Hyperliquid testnet, selected market, one open order |
196
- | `perps.provider_mainnet_readonly` | Read-only balance/provider proofs | Hyperliquid mainnet, no cleanup mutation unless explicitly requested |
197
-
198
- Profiles are shortcuts, not separate hardcoded actions. A recipe should be able to override provider, network, market, account, positions, orders, balance, page, and HUD behavior.
199
-
200
- ## Recipe v1 shape
201
-
202
- Recommended shape for a proof recipe:
203
-
204
- ```json
205
- {
206
- "preconditions": [
207
- "wallet.unlocked",
208
- "perps.feature_enabled",
209
- "perps.trading_flag"
210
- ],
211
- "startState": {
212
- "action": "metamask.perps.start_state",
213
- "intent": "Converge Perps to a clean BTC testnet baseline before proof",
214
- "params": {
215
- "profile": "perps.clean_market_testnet",
216
- "market": "BTC",
217
- "positions": { "state": "none", "market": "BTC" },
218
- "orders": { "state": "none", "market": "BTC" }
219
- },
220
- "record": "trace_only"
221
- },
222
- "proof": {
223
- "nodes": {
224
- "ac-specific-step": {
225
- "action": "ui.press",
226
- "intent": "Perform the AC-specific Perps interaction through the UI",
227
- "test_id": "example",
228
- "record": "proof_window"
229
- }
230
- }
231
- }
232
- }
233
- ```
234
-
235
- The prestate runs before the proof window and remains visible in `trace.json`/`summary.json`; the proof window only records the AC-specific behavior. This preserves review speed without hiding setup from the agent or reviewer.
@@ -1,301 +0,0 @@
1
- # Recipe Libraries
2
-
3
- Per-change recipes are throwaway proofs: generated for one PR, run as evidence,
4
- discarded. A **recipe library** is the durable keep behind them — the reusable
5
- flows ("set up a funded wallet", "converge Perps to a clean BTC baseline") a
6
- per-change recipe is composed FROM. Libraries are owned bottom-up: each
7
- developer accumulates their own, teams share theirs, and this runner ships only
8
- a minimal canonical set. Nothing here requires central governance to function.
9
-
10
- ## The three sources and who owns them
11
-
12
- | Source | Location | Owned by | Contains |
13
- | --- | --- | --- | --- |
14
- | Personal | `<farmslot home>/recipe-library` (`FARMSLOT_HOME`, default `~/.farmslot`) | the developer, entirely | the developer's accumulated flow history |
15
- | Team | a repo the team owns (any host); checked out locally and configured as a source | the team's developers directly — not a MetaMask-governed artifact | richer domain flows the team maintains |
16
- | Canonical (repo-native) | this runner's `library/` | this runner | minimal, stable primitives only (see `library/flows/`) |
17
-
18
- A library is just a directory:
19
-
20
- ```text
21
- <library-root>/
22
- library.json { "kind": "recipe-library", "schema_version": 1, "name": "..." }
23
- flows/*.flows.json standard recipe-flow-catalog documents (Recipe Protocol v1 §8)
24
- ```
25
-
26
- There is no new format: library flows are the same flow-catalog documents a
27
- recipe can reference through `uses`, and per-change recipes call them with the
28
- standard `call` node.
29
-
30
- ## Resolution and precedence
31
-
32
- `mm-harness run` resolves `call` refs from ordered sources; the first
33
- source that declares a ref wins:
34
-
35
- 1. recipe-local declarations (inline `flows` and explicit `uses` paths) — always win;
36
- 2. your explicit entries: repeatable/colon-separated `--library name=path` or `RECIPE_LIBRARY_PATH` (order = precedence), or, when neither is set, your personal library;
37
- 3. the canonical `metamask` library, appended last.
38
-
39
- Personal and team flows therefore shadow canonical ones **by default** — that
40
- is intentional (your history is the point; the canonical set is fallback
41
- vocabulary, not authority). Shadowing is never silent: it is printed at run
42
- start, recorded in `summary.json` under `flowResolution`, and the exact
43
- definitions a run executed are snapshotted into the `resolved-flows.json`
44
- artifact so reviewers can inspect the evidence without access to your
45
- libraries.
46
-
47
- ```bash
48
- mm-harness flows list # every flow across your sources, with provenance
49
- RECIPE_LIBRARY_PATH="team=~/dev/team-recipes" mm-harness flows list
50
- mm-harness run recipe.json --adapter extension --artifacts-dir artifacts \
51
- --library "team=~/dev/team-recipes"
52
- ```
53
-
54
- ## Promoting a flow (building your history)
55
-
56
- After a successful run, keep a flow that proved useful:
57
-
58
- ```bash
59
- mm-harness flows promote --from recipe.json --flow perps.my_baseline \
60
- --run artifacts # stamps provenance.lastVerified from the passing run
61
- ```
62
-
63
- Promotion is the contract gate: the flow must have a `description`, `ensure_*`
64
- flows must declare a `postcondition`, and the stored flow is stamped with
65
- provenance (origin recipe, promotion date, and `lastVerified` only when backed
66
- by a passing run's artifacts). The personal library is created on first
67
- promote.
68
-
69
- ## Sharing with your team
70
-
71
- Sharing is a plain PR: copy or promote the flow into the team's library repo
72
- and let the team review it like any other change. Their repo, their rules —
73
- there is no registration step and no org approval involved.
74
-
75
- ## Trust and drift
76
-
77
- Library flows rot as the app changes. Three guards, cheapest first:
78
-
79
- 1. **Postconditions** — a stale flow fails its postcondition at run time and
80
- fails the recipe loudly; promotion nudges every flow toward asserting its
81
- outcome.
82
- 2. **Freshness warnings** — `flows list` and run-start resolution flag flows
83
- whose `lastVerified` is older than 30 days.
84
- 3. **Provenance in evidence** — `flowResolution` + `resolved-flows.json` make
85
- it visible exactly which flow, from which source, produced a proof.
86
-
87
- ## Rules for the canonical library
88
-
89
- Additions to `library/flows/` are held to the flow-catalog maintenance rules
90
- (`docs/perps-flow-catalog.md`, Recipe Protocol v1 §8.1): parameterized
91
- primitives over name multiplication, a real reusable domain concept per flow,
92
- and a small flow budget. If a flow is team- or task-specific, it belongs in a
93
- team or personal library — that is what the precedence order is for.
94
- `scripts/check.mjs` validates every committed catalog against the action
95
- manifests.
96
-
97
- ## Your own measured flow: creating a personal recipe library
98
-
99
- A **measured flow** is a recipe you run the same way every time to watch how long
100
- each user-visible step takes. The runner has no benchmark verb: timings are just
101
- the per-node `duration`s in a passing run's `trace.json`, so a flow is comparable
102
- across runs only when you pin the run (same device, healing off) and keep the node
103
- graph stable. The repo ships one canonical example,
104
- `library/recipes/perps/performance.mobile.recipe.json` — unlock → open the Perps
105
- market list → read live state → open a market detail. This walkthrough copies it
106
- into a library of your own and retargets it to your journey. A peer engineer can
107
- follow it verbatim; the same steps run as the `perps.performance-recipe` contract
108
- test.
109
-
110
- ### 1. Scaffold a personal library
111
-
112
- A library is a directory with a `library.json` marker. Keep reusable `flows/`
113
- (referenced via `call`) beside a domain-grouped `recipes/` folder for the full
114
- flows you run:
115
-
116
- ```bash
117
- mkdir -p ~/my-recipes/flows ~/my-recipes/recipes/my-perps
118
- cat > ~/my-recipes/library.json <<'JSON'
119
- { "kind": "recipe-library", "schema_version": 1, "name": "mydev", "owner": "mydev" }
120
- JSON
121
- ```
122
-
123
- In real use this is discovered for you: with no `--library` flag and no
124
- `RECIPE_LIBRARY_PATH`, the runner reads your personal library at
125
- `$FARMSLOT_HOME/recipe-library` (default `~/.farmslot/recipe-library`). The
126
- explicit `--library mydev=<dir>` form below is the same mechanism, spelled out so
127
- it works headlessly (CI, a scratch checkout) and so the path is unambiguous.
128
-
129
- ### 2. Copy the canonical recipe as a starting point
130
-
131
- Copy it out of your runner checkout's `library/recipes/`:
132
-
133
- ```bash
134
- cp library/recipes/perps/performance.mobile.recipe.json \
135
- ~/my-recipes/recipes/my-perps/performance.mobile.recipe.json
136
- ```
137
-
138
- ### 3. Edit the nodes to your journey
139
-
140
- Open the copy and change what you measure while keeping the measured-flow shape.
141
- Retarget `open-market-detail` to your market and add one extra measured step —
142
- here, reading live orders on the detail screen:
143
-
144
- ```jsonc
145
- "open-market-detail": {
146
- "action": "ui.navigate",
147
- "page": "perps-market",
148
- "market": "ETH", // was BTC
149
- "intent": "Open the ETH Perps market detail screen",
150
- "next": "read-orders" // was "end"
151
- },
152
- "read-orders": { // your extra measured step
153
- "action": "metamask.perps.read_orders",
154
- "market": "ETH",
155
- "intent": "Read live ETH orders on the market detail screen",
156
- "next": "end"
157
- }
158
- ```
159
-
160
- The canonical recipe keeps its nodes inline — a clean measured baseline. For
161
- bigger journeys you can extract repeated setup steps into a personal `flows/`
162
- segment and `call` it. For example, define an unlock + open-Perps-list segment
163
- in `~/my-recipes/flows/mydev.flows.json`:
164
-
165
- ```json
166
- {
167
- "schema_version": 1, "kind": "recipe-flow-catalog", "owner": "mydev",
168
- "flows": {
169
- "mydev.open_perps_setup": {
170
- "version": 1,
171
- "description": "Unlock the wallet and open the Perps market list.",
172
- "workflow": {
173
- "entry": "ensure-unlocked",
174
- "nodes": {
175
- "ensure-unlocked": {
176
- "action": "metamask.wallet.ensure_unlocked",
177
- "intent": "Unlock the wallet before the Perps journey",
178
- "next": "open-perps-list"
179
- },
180
- "open-perps-list": {
181
- "action": "ui.navigate",
182
- "page": "perps",
183
- "intent": "Open the Perps market list screen",
184
- "next": "done"
185
- },
186
- "done": { "action": "end", "status": "pass" }
187
- }
188
- }
189
- }
190
- }
191
- }
192
- ```
193
-
194
- Then replace the two inline setup nodes in your recipe with a single `call` node:
195
-
196
- ```jsonc
197
- "setup": {
198
- "action": "call",
199
- "ref": "mydev.open_perps_setup",
200
- "intent": "Run the personal Perps setup segment (unlock + open list)",
201
- "next": "read-positions"
202
- }
203
- ```
204
-
205
- The segment validates via `--plan` — the plan step verifies the `call` ref
206
- resolves from the library. Keep flow segment actions within the mobile action
207
- surface: the flow catalog format has no `platform`/`adapter` dimension today,
208
- so a flow using a core-only action (e.g. `command`) will pass `--plan` on mobile
209
- but fail at live-run time with "No adapter registered for flow action X". There
210
- is no plan-time cross-adapter enforcement; that gap would require an `adapters`
211
- field on flow catalog entries — not yet in the protocol.
212
-
213
- ### 4. Run it and read the timings
214
-
215
- Validate statically **by name** first — `run` probes each library source's
216
- `recipes/` directory in precedence order (personal → team → canonical), so
217
- `my-perps.performance` resolves from
218
- `~/my-recipes/recipes/my-perps/performance.mobile.recipe.json` without you
219
- spelling out the path:
220
-
221
- ```bash
222
- # Static validation by NAME — resolves from the personal library via --library.
223
- mm-harness run my-perps.performance \
224
- --library mydev=~/my-recipes --plan --adapter mobile
225
-
226
- # Zero-flag personal-library: when ~/my-recipes is placed at
227
- # $FARMSLOT_HOME/recipe-library (default ~/.farmslot/recipe-library), the runner
228
- # discovers it automatically and run-by-name works without --library:
229
- mm-harness run my-perps.performance --plan --adapter mobile
230
-
231
- # Pinned live run — same device, healing OFF, so durations are comparable.
232
- mm-harness run my-perps.performance \
233
- --library mydev=~/my-recipes \
234
- --adapter mobile --device <serial> --heal off --artifacts-dir artifacts
235
-
236
- # Canonical start-state variants — same installed app, no rebuild.
237
- mm-harness run app.lifecycle.android-smoke \
238
- --adapter mobile --device <serial> --heal off --artifacts-dir artifacts/lifecycle-smoke
239
- mm-harness run perps.performance.warm-start \
240
- --adapter mobile --device <serial> --heal off --artifacts-dir artifacts/warm
241
- mm-harness run perps.performance.background-resume \
242
- --adapter mobile --device <serial> --heal off --artifacts-dir artifacts/background
243
- mm-harness run perps.performance.cold-start \
244
- --adapter mobile --device <serial> --heal off --artifacts-dir artifacts/cold
245
-
246
- # Per-node durations to diff across runs (trace.json is an array of entries, or
247
- # { metadata, entries: [...] }; each entry carries nodeId + durationMs):
248
- node -e 'const t=require("./artifacts/trace.json"); \
249
- for (const e of Array.isArray(t)?t:t.entries) console.log(e.nodeId, e.durationMs)'
250
- ```
251
-
252
- A miss with `--library` names the sources that were searched, so you can tell at a
253
- glance whether a typo or a missing library entry caused the failure.
254
-
255
- Run `app.lifecycle.android-smoke` first when validating a new Android slot; it
256
- isolates lifecycle control from wallet setup and Perps navigation. The start-state
257
- variants use the standard outer `app.lifecycle` action. Every
258
- variant begins with `app.status` so `trace.json` records an idempotent start
259
- marker before lifecycle setup and Perps timing nodes. Android background resume
260
- sends HOME, then relaunches through the Expo dev-client deep link. Cold start
261
- force-stops the installed package, then launches the same build through the deep
262
- link.
263
-
264
- ### The measured-flow pattern
265
-
266
- Five rules keep timings meaningful and diffable:
267
-
268
- 1. **Pin the run** — `--device <serial> --heal off`. Healing retries hide the
269
- regressions you are trying to measure.
270
- 2. **One node per user-visible step** — a node's `duration` is only a signal when
271
- it maps to a single thing the user sees.
272
- 3. **Stable, human-meaningful node keys** — operators diff node keys across runs;
273
- renaming `open-market-detail` breaks every historical comparison.
274
- 4. **No destructive side effects** — a measured flow should be repeatable. The
275
- canonical recipe stops at read + navigate; add `place_order`/`close` only in a
276
- personal copy when you deliberately want to measure the trade path.
277
- 5. **Read timings from `trace.json`, not the console** — the trace is the durable
278
- per-node record; the console is for humans watching the run.
279
-
280
- ### Shadowing, in practice
281
-
282
- Because `--library` sources resolve `call` refs before the canonical `metamask`
283
- library, a personal flow named like a canonical one shadows it — your history is
284
- the point. You can watch this happen on mobile with the segment recipe from the
285
- walkthrough above:
286
-
287
- ```bash
288
- # A mobile recipe with "action": "call", "ref": "mydev.open_perps_setup"
289
- # fails without the library source…
290
- mm-harness run my-perps-with-segment --plan --adapter mobile
291
- # → workflow.unresolved_call_ref
292
-
293
- # …and resolves once the personal library is on the path:
294
- mm-harness run my-perps-with-segment --plan --adapter mobile \
295
- --library "mydev=~/my-recipes"
296
- # → plan pass
297
- ```
298
-
299
- That resolution — unresolved without the source, `pass` with it — is what the
300
- `perps.performance-recipe` contract test asserts, alongside the canonical recipe
301
- resolving by name and the personal mobile copy planning by path and basename.
@@ -1,36 +0,0 @@
1
- # Runtime File Conventions
2
-
3
- This runner uses a small set of file extensions on purpose. The goal is to keep
4
- MetaMask-specific logic easy to run from either a source checkout or a published
5
- package without requiring target app builds to transpile runner code.
6
-
7
- ## Extension rules
8
-
9
- - `src/**/*.ts` — typed runner core: CLI parsing, manifests, adapter binding,
10
- runtime decisions, and shared helper logic.
11
- - `library/actions/**/*.mjs`, `adapters/**/*.mjs`, and `scripts/**/*.mjs` —
12
- standalone ESM modules that Node executes directly with no build step. Use
13
- these for action adapters and small injected/runtime helpers.
14
- - `*.cjs` — compatibility islands only. Keep these quarantined for helper code
15
- that intentionally needs CommonJS semantics, such as portable `require()`
16
- execution from shell scripts or bridge code shared with older runtime contexts.
17
- - `*.sh` — thin OS/device orchestration wrappers for tools such as `simctl`,
18
- `adb`, Chrome launch, tmux, and filesystem setup. Do not put recipe graph
19
- execution or MetaMask domain semantics in shell.
20
- - Plain source `*.js` is not allowed. With `"type": "module"`, `.js` would be
21
- ESM, but it is visually ambiguous in this runner. Use `.mjs` for direct Node
22
- scripts or `.ts` for typed core code.
23
-
24
- ## Design intent
25
-
26
- The mix is intentional only when the boundary is clear:
27
-
28
- 1. TypeScript owns maintainable product/runner decisions.
29
- 2. ESM scripts own no-build runtime adapters and injected helpers.
30
- 3. CommonJS stays isolated where the runtime context makes ESM brittle.
31
- 4. Shell stays at the edge for host/device commands.
32
-
33
- If a file crosses those boundaries, move the logic inward: shell should call a
34
- Node module, standalone `.mjs` should become typed `src/**/*.ts` when it grows
35
- shared domain logic, and new compatibility needs should be documented before
36
- adding another `.cjs` file.
@@ -1,64 +0,0 @@
1
- {
2
- "schema_version": 1,
3
- "kind": "recipe-flow-catalog",
4
- "owner": "metamask.perps",
5
- "flows": {
6
- "perps.clean_market_testnet": {
7
- "version": 1,
8
- "description": "Converge Perps to a clean testnet baseline before a proof window: wallet unlocked, requested market selected, no open positions or orders for that market.",
9
- "paramsSchema": {
10
- "type": "object",
11
- "required": ["market"],
12
- "properties": {
13
- "market": {
14
- "type": "string",
15
- "description": "Market symbol to converge, e.g. BTC."
16
- },
17
- "provider": {
18
- "type": "string",
19
- "description": "Optional Perps provider, e.g. hyperliquid; omit for the app default."
20
- }
21
- }
22
- },
23
- "requiresActions": ["metamask.wallet.ensure_unlocked", "metamask.perps.start_state"],
24
- "defaultPhase": "start_state",
25
- "defaultRecord": "trace_only",
26
- "postcondition": { "path": "$.status", "operator": "eq", "value": "pass" },
27
- "workflow": {
28
- "entry": "ensure-wallet",
29
- "nodes": {
30
- "ensure-wallet": {
31
- "action": "metamask.wallet.ensure_unlocked",
32
- "intent": "Ensure the wallet is unlocked before converging the Perps baseline",
33
- "next": "start-state"
34
- },
35
- "start-state": {
36
- "action": "metamask.perps.start_state",
37
- "intent": "Converge Perps to a clean {{params.market}} testnet baseline",
38
- "profile": "clean_market_testnet",
39
- "network": "testnet",
40
- "provider": "{{params.provider}}",
41
- "market": "{{params.market}}",
42
- "positions": { "state": "none", "mode": "matching" },
43
- "orders": { "state": "none", "mode": "matching" },
44
- "next": "done"
45
- },
46
- "done": { "action": "end", "status": "pass" }
47
- }
48
- },
49
- "examples": [
50
- {
51
- "description": "Clean BTC testnet baseline before a visual proof",
52
- "node": {
53
- "action": "call",
54
- "ref": "perps.clean_market_testnet",
55
- "params": { "market": "BTC" },
56
- "phase": "start_state",
57
- "record": "trace_only",
58
- "intent": "Converge Perps to a clean BTC testnet baseline before proof"
59
- }
60
- }
61
- ]
62
- }
63
- }
64
- }