@deeeed/metamask-harness 0.18.0 → 0.19.1

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 (82) hide show
  1. package/CHANGELOG.md +200 -0
  2. package/README.md +4 -7
  3. package/adapters/core/inject.sh +1 -6
  4. package/adapters/extension/inject.mjs +1 -4
  5. package/adapters/extension/verify.sh +1 -1
  6. package/adapters/mobile/bridge-runtime/cdp-bridge.cjs +33 -0
  7. package/adapters/mobile/inject.sh +5 -4
  8. package/adapters/mobile/verify.sh +15 -19
  9. package/adapters/shared/harness-source-fingerprint.mjs +14 -12
  10. package/dist/adapters/harness-freshness.js +49 -0
  11. package/dist/adapters.js +6 -1
  12. package/dist/cli.js +0 -4
  13. package/dist/command-contract.js +51 -40
  14. package/dist/command-journal.js +50 -12
  15. package/dist/commands/call.js +95 -20
  16. package/dist/commands/check.js +1 -1
  17. package/dist/commands/completion-candidates.js +7 -19
  18. package/dist/commands/fixtures.js +50 -1
  19. package/dist/commands/last.js +9 -1
  20. package/dist/commands/launch/extension.js +2 -2
  21. package/dist/commands/launch/mobile.js +2 -0
  22. package/dist/commands/list-executables.js +56 -20
  23. package/dist/commands/manifest.js +28 -12
  24. package/dist/commands/parse-args.js +55 -3
  25. package/dist/commands/provision.js +0 -1
  26. package/dist/commands/run-engine.js +514 -336
  27. package/dist/commands/run.js +25 -37
  28. package/dist/commands/shared.js +1 -1
  29. package/dist/heal-bounds.js +5 -0
  30. package/dist/live-adapter-contract.js +15 -2
  31. package/dist/mm-harness-cli.js +22 -30
  32. package/dist/run-diagnostics.js +1 -1
  33. package/dist/run-recording.js +1 -1
  34. package/dist/runner.js +24 -2
  35. package/docs/CONTRIBUTING.md +2 -3
  36. package/docs/QA.md +0 -1
  37. package/docs/RECIPES.md +67 -91
  38. package/library/README.md +5 -5
  39. package/library/actions/core/perps/_controller.mjs +33 -1
  40. package/library/actions/core/perps/assert_orders.mjs +22 -12
  41. package/library/actions/core/perps/assert_positions.mjs +22 -12
  42. package/library/actions/core/perps/close_orders.mjs +2 -0
  43. package/library/actions/core/perps/close_positions.mjs +2 -0
  44. package/library/actions/core/perps/ensure_orders.mjs +4 -2
  45. package/library/actions/core/perps/ensure_positions.mjs +4 -2
  46. package/library/actions/core/perps/place_order.mjs +7 -3
  47. package/library/actions/extension/perps/assert_orders.mjs +2 -1
  48. package/library/actions/extension/perps/assert_positions.mjs +2 -1
  49. package/library/actions/extension/perps/perps.mjs +151 -30
  50. package/library/actions/mobile/perps/assert_orders.mjs +2 -1
  51. package/library/actions/mobile/perps/assert_positions.mjs +2 -1
  52. package/library/actions/mobile/perps/perps.mjs +148 -22
  53. package/library/library.json +1 -1
  54. package/library/manifests/core.action-manifest.json +1204 -411
  55. package/library/manifests/extension.action-manifest.json +1536 -656
  56. package/library/manifests/mobile.action-manifest.json +1728 -758
  57. package/library/recipes/app/lifecycle.android-smoke.mobile.recipe.json +63 -81
  58. package/library/recipes/perps/clean-market-testnet.core.recipe.json +44 -0
  59. package/library/recipes/perps/clean-market-testnet.recipe.json +49 -0
  60. package/library/recipes/perps/lifecycle.recipe.json +136 -180
  61. package/library/recipes/perps/order-lifecycle.core.recipe.json +71 -67
  62. package/library/recipes/perps/performance.background-resume.mobile.recipe.json +51 -67
  63. package/library/recipes/perps/performance.cold-start.mobile.recipe.json +51 -67
  64. package/library/recipes/perps/performance.mobile.recipe.json +37 -51
  65. package/library/recipes/perps/performance.warm-start.mobile.recipe.json +44 -59
  66. package/library/recipes/perps/read-markets.core.recipe.json +29 -31
  67. package/library/recipes/perps/smoke.core.recipe.json +29 -32
  68. package/library/recipes/perps/smoke.extension.recipe.json +41 -44
  69. package/library/recipes/perps/smoke.mobile.recipe.json +42 -44
  70. package/library/recipes/perps/trading-lifecycle.core.recipe.json +69 -65
  71. package/library/recipes/runner/action-validation.extension.recipe.json +312 -405
  72. package/library/recipes/runner/action-validation.mobile.recipe.json +316 -409
  73. package/library/recipes/runner/smoke.core.recipe.json +18 -20
  74. package/library/recipes/runner/smoke.extension.recipe.json +23 -24
  75. package/library/recipes/runner/smoke.mobile.recipe.json +23 -24
  76. package/library/recipes/wallet/smoke.extension.recipe.json +33 -35
  77. package/library/recipes/wallet/smoke.mobile.recipe.json +33 -35
  78. package/package.json +3 -3
  79. package/scripts/completions.sh +1 -4
  80. package/dist/adapters/extension/harness-freshness.js +0 -39
  81. package/dist/commands/flows.js +0 -91
  82. package/library/flows/perps.flows.json +0 -64
package/CHANGELOG.md CHANGED
@@ -2,6 +2,58 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.19.1 - 2026-07-23
6
+
7
+ ### Added
8
+
9
+ - Mobile `ui.press` supports `long_press=true` for real long-press gestures such as resetting a keypad value.
10
+
11
+ ### Fixed
12
+
13
+ - Perps position and order assertions honor `timeout_ms` consistently on Mobile, Extension, and Core.
14
+ - Core Perps reads and assertions honor their explicit network instead of silently defaulting isolated actions to testnet.
15
+ - Mobile and Extension `ensure_orders state=open` create a resting testnet limit order when missing and recognize live buy/sell sides consistently.
16
+ - Reject invalid live-adapter process timeouts and use the safe default.
17
+
18
+ ## 0.19.0 - 2026-07-22
19
+
20
+ ### Added
21
+
22
+ - Discover a task recipe's adjacent `recipe-library/` without extra flags.
23
+ - Recipe execution now accepts typed root `key=value` parameters, applies defaults, and resolves nested recipes through the same adapter-aware index.
24
+ - `run --list` and `run --describe` now expose parameter defaults, adapter variants, nested recipes, sources, and shadows.
25
+ - Runs now retain exact digest-keyed dependency recipes and `recipe-resolution.json`.
26
+
27
+ ### Changed
28
+
29
+ - Made bare `mm-harness fixtures` report the current fixture status and next action instead of failing with an option dump.
30
+ - Simplified CLI usage failures and `run --help`; removed duplicate `--project-root`, `--record`, and logical-adapter `--platform` spellings while retaining `--platform ios|android`.
31
+ - Direct calls teach missing/invalid values, show declared defaults, omit routine library provenance, and reject implicit Perps assertion/mutation targets.
32
+ - Perps reads without a selector consistently return all live items across Extension, Mobile, and Core.
33
+ - `run` errors lead to recipe discovery instead of dumping every flag; help separates common overrides from managed integration controls.
34
+ - Declared team and task-library actions retain source provenance through the packaged adapter path.
35
+ - Validate every bundled recipe dependency graph during `yarn check` so missing or cyclic `call.ref` values fail before runtime.
36
+ - Require Farmslot protocol `^0.11.0` and recipe-harness `^0.9.0` for composable Recipe v1 execution.
37
+ - Discovery now exposes callable actions through `call --list` and executable recipes through `run --list`.
38
+
39
+ ### Fixed
40
+
41
+ - Deduplicate recipe-library output and reduce application failures to their actionable message while retaining full trace detail.
42
+ - `last` now shows the redacted command and evidence paths, including automatically resolved run/call artifact directories.
43
+ - `run --describe` reports the recipe's exact `$schema` URL instead of a fabricated nested version.
44
+ - `run --plan` rejects invalid parameters passed into nested recipes.
45
+ - Command journals redact sensitive keys inside standalone JSON arguments.
46
+ - Core Perps actions support the current Core `tsx` module export shape.
47
+ - Recipe delegation preserves upstream non-zero exit status and uses the public `mm-harness` name in recovery commands.
48
+ - Recipe discovery and repeated `--library` flags preserve every source in declared precedence order.
49
+
50
+ ### Removed
51
+
52
+ - Removed redundant `--project-root`, bare `--record`, and logical-adapter uses of `run`/`call --platform`; use `--target`, `--record-video`, and `--adapter`.
53
+ - Removed flow catalogs, flow promotion, the `flows` command, and their completion surface; reusable graphs are composable Recipe v1 documents.
54
+
55
+ ## 0.18.0 - 2026-07-20
56
+
5
57
  ### Added
6
58
 
7
59
  - Recipe runs now preflight source provenance and action risk before runtime preparation; untrusted restricted plans require exact-digest approval.
@@ -98,11 +150,13 @@
98
150
  - `checklist mark ... complete` now teaches the exact compact quality input, canonical builder command, and coverage artifact when a recipe activates those terminal requirements, instead of forcing agents to reverse-engineer the artifact validator.
99
151
 
100
152
  ### Added
153
+
101
154
  - Checkout-scoped operation locking prevents concurrent launch, fixture mutation, and recipe execution in one repository while allowing other checkouts/slots to run independently; stale locks from exited processes are reclaimed.
102
155
  - `doctor --fix` creates an ignored, checkout-scoped `agentic-runtime.json` when absent, with stable claimed local ports for ordinary standalone clones while preserving explicit or Farmslot-provided slot resources.
103
156
  - `fixtures init --from <path>|--dev` initializes the canonical checkout wallet fixture explicitly; `--dev` is a disposable public test wallet and is never suitable for real funds.
104
157
 
105
158
  ### Fixed
159
+
106
160
  - Extension `debug` now opens a working, offline DevTools console attached to the running slot's extension page: it launches the locally-served bundled DevTools frontend in its own window inside the slot's Chromium instead of Chrome's hosted `chrome-devtools-frontend.appspot.com` URL, which Chrome's WebSocket origin check rejected (the console showed "Debugging connection was closed"). A slot launched before this change fails a pre-open origin probe and reports the exact relaunch command instead of opening a dead console.
107
161
  - `doctor --fix` now schema-validates, quarantines, and recreates malformed, semantically invalid, or cross-adapter-contaminated checkout runtime context without touching product data; wallet-fixture recovery commands use `--force` only for invalid or incomplete existing fixtures, while a missing fixture retains the safe unforced initialization command.
108
162
  - `logs --source app` now resolves the adapter-owned application console (`extension-console.log` on Extension and the CDP-forwarded `metro.log` on Mobile); an absent console returns `APP_LOG_UNAVAILABLE` instead of falling back to a dev-server log or reporting a usage error.
@@ -135,23 +189,27 @@
135
189
  ## 0.14.7 - 2026-07-10
136
190
 
137
191
  ### Changed
192
+
138
193
  - `doctor --print-ready` now implies the exit-coded live probe — Farmslot `health_check` hooks need only `--print-ready`, not `--expect-live --print-ready`.
139
194
  - Mobile `--print-ready` uses bridge `walletState` (unlocked + live bridge), not React Navigation route names; mobile `ready_indicator` is `OK`.
140
195
  - Removed `metamask-recipe` install symlinks from adapter inject paths (no compat aliases).
141
196
  - `doctor --print-ready` rejects `--json` (stdout is reserved for the Farmslot indicator line).
142
197
 
143
198
  ### Fixed
199
+
144
200
  - Mobile `surface.runtimeStatus` infers Android from `ADB_SERIAL` / `ANDROID_SERIAL` when present.
145
201
 
146
202
  ## 0.14.6 - 2026-07-10
147
203
 
148
204
  ### Added
205
+
149
206
  - `mm-harness doctor --print-ready` — Farmslot `health_check` mode: prints `health.ready_indicator` on stdout (`extension`/`mobile`: `OK`; `core`: `ready`).
150
207
  - `doctor --expect-live` — exit-coded liveness gate without indicator output (prepare recovery and other pass/fail callers).
151
208
 
152
209
  ## 0.14.5 - 2026-07-10
153
210
 
154
211
  ### Fixed
212
+
155
213
  - Extension `ensure-browser` closes disposable New Tab / blank / extensions pages after reopen, matching reattach/ensure-ready hygiene.
156
214
  - Extension slot browser titles are stamped through one shared helper (`adapters/extension/lib/slot-title.cjs`) with a persistent `MutationObserver`, and re-applied after `ensureExtensionReady` open/prune/reopen so relaunch and recipe paths keep `<slot-id> — MetaMask` instead of a bare MetaMask window.
157
215
  - Extension slot-title contract test is hermetic (local CDP/WebSocket stub + fake DOM); Playwright and CDP stamps share one function source so they cannot drift.
@@ -160,11 +218,13 @@
160
218
  ## 0.14.4 - 2026-07-09
161
219
 
162
220
  ### Fixed
221
+
163
222
  - Include the new extension viewer cleanup scripts in the adapter manifest so published packages pass adapter-surface validation.
164
223
 
165
224
  ## 0.14.3 - 2026-07-09
166
225
 
167
226
  ### Fixed
227
+
168
228
  - Extension `status` and `doctor` now accept a sidepanel-only live runtime, matching `run`/`call` health checks instead of requiring a fullscreen `home.html` tab after `launch --sidepanel`.
169
229
  - Extension sidepanel launch waits longer for Chrome to expose the `sidepanel.html` CDP target, avoiding false failures after Chrome accepts the open request.
170
230
  - Extension tmux viewers are opened only in the run-owned tmux session and are cleaned by `mm-harness stop`, preventing stale `webpack-*`/`console-*` windows from leaking across slots.
@@ -173,15 +233,18 @@
173
233
  ## 0.14.2 - 2026-07-09
174
234
 
175
235
  ### Fixed
236
+
176
237
  - Require `@farmslot/recipe-harness` `^0.4.3` so runtime dependency readiness trusts install markers newer than an old recorded baseline and stops repeatedly prompting managed slots to reinstall fresh dependencies.
177
238
 
178
239
  ## 0.14.1 - 2026-07-09
179
240
 
180
241
  ### Added
242
+
181
243
  - `mm-harness check diff` runs bounded repo-local validation for the active git diff, writing `validation-summary.json`/`.md` plus per-check logs. The fast profile runs changed-file ESLint, Prettier, and changed test files; `--profile full` also runs the repo typecheck when available. The default base follows the active PR base or repo remote HEAD before falling back to common branch names.
182
244
  - `mm-harness run` writes a concise human report (`report.md`) and prints a colored step summary for every adapter, so successful Core/Mobile/Extension recipe runs are scannable without opening raw trace files.
183
245
 
184
246
  ### Fixed
247
+
185
248
  - Mobile slot targeting now refuses a platform launch/run/call when `--platform ios|android` conflicts with the configured default target, preventing an iOS-pinned slot from silently stealing a shared Android device; `status` and `doctor` scope device lists to the configured target by default and `status` reports other reachable bridges as additional targets that can be driven with `--device`.
186
249
  - Mobile `status` bases `Next:` on the configured default target only, avoids assigning an additional Android bridge to a same-platform selected device, and reports stale Android reverse ports when the selected device is not connected to the slot Metro.
187
250
  - Mobile `doctor` now reports the same additional reachable targets as `status`, so diagnostics and the home view agree on which devices can be driven with `--device`.
@@ -198,11 +261,13 @@
198
261
  ## 0.14.0 - 2026-07-08
199
262
 
200
263
  ### Added
264
+
201
265
  - `mm-harness call` accepts action fields as direct `key=value` shorthand in addition to repeated `--arg k=v`, so commands like `mm-harness call ui.navigate page=perps` now match the examples agents naturally write.
202
266
  - Packaged recipes are grouped by domain under `library/recipes/<domain>/` and still resolve by dotted names (`perps.smoke`, `runner.smoke`) with legacy aliases for the former flat names.
203
267
  - Packaged `perps.smoke` recipes for mobile, extension, and core provide an explicit non-mutating Perps domain smoke. The existing bare `smoke` alias remains a runner/transport smoke for compatibility.
204
268
 
205
269
  ### Fixed
270
+
206
271
  - The top-level help SLOT banner now labels the slot dev-server port by adapter, so extension checkouts show `webpack :<port>` instead of the mobile-only `metro :<port>`.
207
272
  - Hidden internal runtime commands remain executable through the installed `mm-harness` bin, fixing Extension verify warnings where `runtime-decision` returned `{}` because the wrapper blocked its own shipped helper.
208
273
  - Extension launch/readiness now stamps the live home tab title with the Farmslot slot id after MetaMask has settled, so headed browser windows identify their owning slot.
@@ -215,11 +280,13 @@
215
280
  ## 0.13.0 - 2026-07-08
216
281
 
217
282
  ### Added
283
+
218
284
  - Typed cdp-bridge failure codes (`NO_TARGET`, `CDP_TIMEOUT`, `WS_CLOSED`, `METRO_UNREACHABLE`): the bridge classifies a failure at its source and reports it three ways a caller can recover — an `ERROR[<CODE>]:` stderr marker, a code-specific exit status (10–13), and a `Next:` teaching line for the caller's actual situation. `bridge.mjs` attaches the code to the error it throws, and `app.hud` lifecycle-skip classification (`isMobileHudLifecycleSkip`) branches on the code first, keeping the substring needles only as a fallback for output from a bridge that predates the codes. The needle fallback is now case-insensitive, closing a gap where target-discovery's `Pinned Android device …` (capital A) missed a lowercase needle.
219
285
  - Console-forwarder now expands object/array console arguments in `metro.log` from the Hermes-supplied inline `preview` (e.g. `{ symbol: "BTC", px: 42 }` instead of the literal `Object`), bounded one level deep and rendered synchronously so the stream never blocks; objects without a preview degrade to their description.
220
286
  - `mm-harness call <action> --help` now renders the named action's own field schema (name, type, required, description, example — from the action manifest) above the generic call flags, instead of printing only the generic call help. Fuzzy short-name resolves like `call` (an ambiguous prefix shows every match); an unresolvable name falls back to the generic help plus a pointer to `mm-harness actions`. `run <recipe> --help` is unchanged (recipe-specific help is a separate, larger surface — recipes are not in the action manifest).
221
287
 
222
288
  ### Fixed
289
+
223
290
  - Runner construction no longer fails with `Manifest action app.lifecycle has no registered adapter` when a manifest declaring `app.lifecycle` is used with a non-mobile adapter (e.g. `call --adapter core --action-manifest library/manifests/mobile.action-manifest.json`): lifecycle adapter registration is manifest-driven instead of gated on the mobile adapter. Executing the action outside a mobile run still fails explicitly at target resolution.
224
291
  - Mobile run teardown always clears any HUD step left painted on-device, so a failed run no longer strands a FAIL banner for the next run. Best-effort and bounded: a down/transitioning bridge simply has nothing to clear and never masks the run's real outcome.
225
292
  - start-metro quick-launch now applies the dev Sentry DSN remap (`MM_SENTRY_DSN` defaults to `MM_SENTRY_DSN_DEV` when unset) at both launch paths. The quick-launch runs `expo start` directly and bypasses `scripts/build.sh`, which normally performs this remap, so without it Sentry never initialized in quick-launched dev clients.
@@ -228,9 +295,11 @@
228
295
  ## 0.12.0 - 2026-07-08
229
296
 
230
297
  ### Added
298
+
231
299
  - Mobile console-forwarder: restores the device console (incl. DevLogger) in `metro.log` on RN 0.79+/Bridgeless via persistent CDP streaming (`Runtime.consoleAPICalled`) — one session per device JS-runtime page (shared target-discovery ranking, never the native C++ page), buffer replay + persisted last-seen cursor (timestamp + same-stamp texts) deduping across reconnects/app reloads, auto-started by `start-metro`; opt out with `METAMASK_RECIPE_CONSOLE_FORWARD=0`. Grep `[console:` for device lines. Zero-dependency transport (built-in Node 22+ WebSocket, like the CDP bridge client). Yields to interactive React Native DevTools: a session evicted by another debugger with no bridge lock present backs off for 5 minutes instead of evicting the human every discovery tick. Idles at the slow poll cadence while Metro is down; flushes pending lines and dedupe state synchronously on SIGTERM/SIGINT.
232
300
 
233
301
  ### Fixed
302
+
234
303
  - `app.hud` no longer aborts recipes on `CDP message timeout` / `WebSocket closed` during dev-client lifecycle transitions (0.11.0 regression that failed every warm-start run at `foreground-app`/`ensure-unlocked`). Skips are loud: the reason is written to stderr and recorded in the action result, so a masked real failure remains visible.
235
304
  - cdp-bridge takes a runtime lock (`cdp-bridge.lock`) so the console-forwarder yields the single debugger slot during bridge commands — prevents mutual eviction and lost `Runtime.evaluate` replies. The forwarder honors the lock while the pid inside it is alive (`kill -0`), so long commands (wallet setup runs `CDP_TIMEOUT=120000`) are never evicted mid-flight while a crashed/killed bridge releases immediately; the bridge releases on SIGTERM/SIGINT, only unlinks a lock it still owns (last-writer-wins under overlap), and takes no lock for `--help`/unknown commands.
236
305
  - `stop-metro` and mobile `cleanup` stop the console-forwarder (recorded pid + checkout-scoped sweep), so no orphan forwarder outlives Metro or fights a later one for the debugger slot.
@@ -239,14 +308,17 @@
239
308
  ## 0.11.0 - 2026-07-07
240
309
 
241
310
  ### Added
311
+
242
312
  - Mobile recipes can now use the standard outer `app.lifecycle` action for deterministic performance start states without rebuilding (Android background/foreground/terminate/restart, iOS simulator launch/terminate/restart through the shared Farmslot lifecycle adapter).
243
313
  - `fixtures set --device <serial|udid|name>` now pins mobile fixture sync to the requested device using the same targeting rules as `run`, `call`, and `doctor`.
244
314
  - New lifecycle-controlled measured flows in the packaged library: `app.lifecycle.android-smoke`, `perps.performance.warm-start`, `perps.performance.background-resume`, `perps.performance.cold-start`; performance recipes now keep lifecycle/unlock in `setup[]`/`startState` so measured nodes carry only CUF timings.
245
315
 
246
316
  ### Changed
317
+
247
318
  - `@farmslot/recipe-harness` dependency raised to `^0.4.0` (ships the `app.lifecycle` adapter — the `FARMSLOT_ROOT` local-source fallback is no longer needed at runtime).
248
319
 
249
320
  ### Fixed
321
+
250
322
  - `launch ios --device <udid|name>` again accepts a shutdown simulator target: launch preserves the requested simulator identity and lets `open-device.sh` boot it instead of requiring it to appear in the booted-device list first.
251
323
  - iOS UDID pins are exported as both `SIM_UDID` and simulator name where available, so downstream simulator tooling can use the stable name while preserving the exact pin.
252
324
  - `metamask.wallet.ensure_unlocked` now waits for the pinned agentic bridge target, tolerates transient status drops during the unlocked stability check, and avoids the old full password-unlock fallback for brief bridge gaps.
@@ -258,17 +330,20 @@
258
330
  ## 0.10.0 - 2026-07-07
259
331
 
260
332
  ### Added
333
+
261
334
  - **Canonical `perps.performance` measured flow** (`library/recipes/perps/performance.mobile.recipe.json`): unlock → open the Perps market list → read live state → open a market detail — one node per user-visible step with stable node names, so the per-node `duration`s in `trace.json` are the timings you monitor. Run pinned: `mm-harness run perps.performance --device <serial> --heal off`. Device-proven end-to-end on a physical Pixel.
262
335
  - **`run <name>` resolves personal/team recipe libraries.** Previously only the packaged library was probed by name; custom recipes ran by path. Sources resolve in library-precedence order (personal > team shadow the packaged canonical — a same-named personal recipe wins), path-shaped args never probe libraries, and a miss teaches which sources were searched. Zero-flag default: `$FARMSLOT_HOME/recipe-library`.
263
336
  - **Custom-library walkthrough** ("Your own measured flow" in `docs/recipe-libraries.md`): a peer engineer scaffolds a personal library, copies the canonical flow, retargets the nodes to their journey, and runs it by name. Every step is executed by the `perps.performance-recipe` contract test, so the doc cannot drift from reality.
264
337
  - **`call` declared on mobile and extension manifests** (was core-only) with self-discovery metadata — personal `flows/` segments are now usable from mobile/extension recipes. Honest limitation documented: flows carry no adapter dimension in the protocol yet, so a cross-adapter `call` fails at live-run rather than plan time.
265
338
 
266
339
  ### Fixed
340
+
267
341
  - **A `--device` pin wins target selection end-to-end on dual-platform slots.** Three independently sufficient holes let a pinned android run drive the iOS simulator (observed live): the discovery simulator filter ran before the android pin and the slot's ambient `IOS_SIMULATOR` captured the candidate set; pins were only enforced when more than one candidate existed (a single WRONG candidate was silently accepted); and the wallet actions' status-entry selectors checked the ambient simulator identity first while never matching serials against Metro device names. Fixed at every layer with live-repro contract cases; an unmatchable pin fails fast listing the Metro candidates.
268
342
 
269
343
  ## 0.9.1 - 2026-07-07
270
344
 
271
345
  ### Fixed
346
+
272
347
  - **Metro survives the launching shell.** `start-metro.sh` spawned Metro without SIGHUP protection, so closing the launching tmux window/shell killed it, leaving a stale `metro.pid` and hanging every subsequent bridge/fixtures call. Metro now starts under `nohup` (argv spawn, no `bash -c` string interpolation), and a stale `metro.pid` naming a dead process is detected and cleaned before start.
273
348
  - **`--device <adb serial>` reaches CDP target selection.** The runner's ambiguity gate resolved the serial, but target discovery compared it against Metro's `deviceName` ("Pixel 6 - 16 - API 36") — never a serial — so with an iOS simulator also attached, a recipe pinned to the physical Android device could silently drive the simulator. The serial is now mapped to the Metro identity via `adb -s <serial> shell getprop ro.product.model` with model-prefix matching (scoped to non-simulator targets); an unmatchable or ambiguous pin (two same-model devices) fails fast listing every Metro `/json/list` candidate instead of silently picking one. `--device <serial>` remains the only thing users pass.
274
349
  - **Bridge commands always emit valid JSON.** `get-route` printed the literal string `undefined` when the route was transiently unavailable mid-navigation, so `bridgeCommand()` threw on parse and `waitForRoute()` aborted instead of polling. `get-route`/`navigate`/`go-back` now normalise a missing route to `null`, `bridgeCommand()` treats `''`/`undefined` stdout as not-settled-yet only for transient-legitimate commands, and `waitForRoute()` polls through `null` until timeout — the timeout error carries the expected route, last parsed route, last bridge reply and the device pin.
@@ -277,12 +352,14 @@
277
352
  ## 0.9.0 - 2026-07-07
278
353
 
279
354
  ### Added
355
+
280
356
  - **`status` auto-probes live app state per device** — the `yarn a:status` successor. Static info prints instantly; within a strict 2s window each device line is enriched with `screen=` (active route via the `__AGENTIC__` bridge, Route objects normalised), `wallet=locked|unlocked|onboarding`, `account=<label> (0x1234…abcd)` and `fixture=READY|missing`. `--fast` skips all probes (guaranteed-instant for scripts); `--json` carries the enriched `devices[]` in one envelope. All RN targets attached to the checkout's Metro are probed and matched per device (exact id → exact name → platform-uniqueness → leftover 1:1), so android + ios side by side on one Metro both report. Honest degradation: `no-bridge` (nothing matched/answering) vs `bridge-absent` (target attached but the installed build predates `__AGENTIC__`, rendered with a rebuild hint) — surfacing pre-bridge builds required the target discovery probe to become three-state (agentic/responsive/unreachable) instead of silently dropping them.
281
357
  - **Every action now carries self-discovery metadata.** `action_metadata` (description + example recipe node, derived from the real handler implementations) covers all official and custom actions across the mobile, extension and core manifests — previously `app.status`, `cdp.target` and all 16 `metamask.wallet.*`/`metamask.perps.*` actions were bare names to agents. A new contract guard enforces coverage (including example structure: `node.action` must match), and locks mobile ↔ extension action-set parity with an explicit platform-only allowlist.
282
358
 
283
359
  ## 0.8.0 - 2026-07-07
284
360
 
285
361
  ### Added
362
+
286
363
  - **`run <name>` — the recipe positional resolves packaged-library recipe names.** An existing file path (absolute or cwd-relative) wins; otherwise the arg is a library recipe name, adapter-scoped first (`run smoke` on mobile → `library/recipes/runner/smoke.mobile.recipe.json`), then adapterless (`run perps.lifecycle` → `library/recipes/perps/lifecycle.recipe.json`), then the exact library filename. A miss teaches `RECIPE_NOT_FOUND` naming the library recipes available for the adapter — no more `$LIB`-style path prefixes in docs or demo scripts. A directory shadowing a library name never wins, and path-shaped args (containing a separator) never fall through to the library.
287
364
  - **`--device <udid|serial|name>` — first-class mobile device targeting on `run`, `call`, and `doctor`.** Resolves against connected devices (adb + booted simulators; exact id first, then exact name with an ambiguity teaching error) and sets the same env `launch --device` does. Mobile `run`/`call` **without** `--device` while more than one targetable device is connected (across android + ios; android state `device`, iOS `Booted`) fail fast listing the devices and the `--device` hint, so a recipe never lands on the wrong device silently. `run --plan`/`--list` are exempt; extension/core teach. Errors honor `--json` with structured envelopes.
288
365
  - **`status` gains `devices[]`** (additive): `{platform, id, name, state, selected}` for connected android devices and booted iOS simulators on mobile checkouts — the `yarn a:status` successor; `selected` reflects the current env pinning.
@@ -290,48 +367,58 @@
290
367
  ## 0.7.5 - 2026-07-07
291
368
 
292
369
  ### Fixed
370
+
293
371
  - **`launch --verify` converges duplicate extension home tabs instead of failing prepare forever.** A second bare `home.html` tab can appear beside the operator's slot-titled one outside the harness's control (the extension opens its own tab after the build-freeze reload), and the live verify health gate requires exactly one home tab — but nothing in launch/verify ever collapsed duplicates (`ensureExtensionReady` was only reachable via the retired `ensure-ready` verb), so every preflight retry recounted two tabs and failed. `launch --verify` (extension, with a CDP port) now runs the convergence before the live verify phase.
294
372
  - **Convergence keeps the operator's tab.** With multiple home tabs, `ensureExtensionReady` now prunes the extras keeping the preferred tab — attached (another client is driving it) over custom slot title (`<slot-id> — MetaMask`, how operators tell which agent owns a browser) over first listed — instead of closing all and reopening fresh, which discarded the slot title. The deterministic close-all + fresh-open remains as a one-shot fallback when the kept tab fails the health check, and a failed re-listing during the fallback reports `cdp-unreachable` instead of a stale tab count.
295
373
 
296
374
  ### Changed
375
+
297
376
  - **`EnsureReadyAction` gains `pruned`** — extras were closed keeping the preferred tab; `reopened` now specifically means the pruned keeper failed the health check and one fresh tab was reopened.
298
377
 
299
378
  ## 0.7.4 - 2026-07-07
300
379
 
301
380
  ### Added
381
+
302
382
  - **`fixtures generate`** — renders the extension wallet `fixture-state.json` from a wallet fixture by fronting `adapters/extension/wallet-fixture-state.cjs generate` (no reimplementation). Extension-only (mobile/core teach); requires `--fixture <wallet-fixture.json>` and `--out <fixture-state.json>`, with a `--json` machine envelope and teaching escapes on missing inputs or a non-extension adapter. This is the public surface farm packs call instead of reaching into the internal leaf, so the Extension pack's `setup/generate-fixture.cjs` resolver can be replaced by a one-line `mm-harness fixtures generate` hook.
303
383
  - **`fixtures finalize`** — seeds account labels/selection into the **live** extension over CDP and validates import parity, by fronting `adapters/extension/wallet-fixture-state.cjs seed-cdp` (no reimplementation). Extension-only, post-launch (mobile/core teach); requires `--fixture`, `--state`, `--cdp-port`, `--extension-dir` (optional `--extension-id-file` / `--out`), with a pure `--json` envelope (leaf progress on stderr), the shared `LEAF_MISSING` pre-check, and teaching escapes on missing flags or a non-extension adapter. This is the public surface for the Extension pack's `setup/preflight.sh` account-label finalization, retiring its last `--resolve-script` reach-in into the internal leaf.
304
384
 
305
385
  ### Fixed
386
+
306
387
  - **`fixtures generate` teaches on a missing leaf instead of leaking a node error.** When the `wallet-fixture-state.cjs` leaf is absent (a corrupt packed install), `generate` now pre-checks the leaf on disk and fails with a one-sentence teaching error plus a `Next:` reinstall command (and a clean `--json` `LEAF_MISSING` envelope), instead of surfacing node's opaque "Cannot find module".
307
388
  - **Extension readiness no longer spawns a duplicate home tab on a transient CDP hiccup.** `ensureExtensionReady` treated any `/json/list` failure as an empty tab set, so a list that transiently failed mid-prepare (unlock → home rerender) looked like "no home tab" and opened a second one next to the healthy tab — the following health check then found two targets and failed prepare. `/json/list` now distinguishes "CDP answered: N targets" from "CDP unreachable" (retried a few times), returns `reasonCode: 'cdp-unreachable'` without touching any tab when the state is unknown, confirms a zero count with a second listing before opening, and converges over a few passes so a wrong action is corrected within the call instead of tripping the health check.
308
389
  - **`readiness` no longer opens a duplicate home tab when the existing one is attached.** `findPageTarget` required `webSocketDebuggerUrl`, but Chrome omits that field for any target another client is attached to — during prepare the fixture finalizer / smoke recipe / CDP evals attach to the healthy home tab, so it listed without a wsUrl, looked absent, and `readiness` opened a second `home.html` that then tripped the exactly-one-home health gate. Page existence is now decided by URL in any attach state; the websocket only gates UI inspection (a present-but-attached tab is reported `pagePresent: true`, `pageInspected: false` and left alone instead of duplicated). URL matching also tolerates the `home.html#/` router rewrite and query suffixes. New additive report fields: `pagePresent`, `pageInspected`.
309
390
 
310
391
  ### Changed
392
+
311
393
  - **`install --adapter core` is documented and contract-locked as the public core-runner install surface for farm packs.** It already fronts `adapters/core/inject.sh` and produces the runner delegate at `<harness>/core/runner/bin/mm-harness`; a new CLI-level contract test (`tests/contract/core-install-cli.test.sh`) pins the happy path, `--json` envelope purity, and the adapter-detection teaching escape. Packs call `mm-harness install --adapter core --target <repo>` (inheriting `RECIPE_HARNESS_ROOT` / `FARMSLOT_ROOT`) instead of cloning a runner checkout and invoking the internal leaf directly — no separate top-level command is minted.
312
394
  - **`EnsureReadyResult` gains a machine-readable `action` field** (`none` | `opened` | `reopened` | `skipped`) stating the tab decision the call took, so a consumer distinguishes "opened one because a zero was confirmed" from "touched nothing because CDP state was unknown" straight from the JSON envelope.
313
395
 
314
396
  ## 0.7.3
315
397
 
316
398
  ### Fixed
399
+
317
400
  - **`provision runway` gates its install-skip on artifact identity.** The baseline now records the installed artifact identity (run id, branch, digest) at install time and a skip preserves it; provisioning skips ONLY when the recorded identity matches the requested (or probe-resolved) run — a different or unknown installed app is reinstalled from the cache instead of being silently kept. Previously any installed MetaMask.app suppressed the install, so a stale binary could be served against a newer JS bundle.
318
401
  - **Provision decisions are machine-readable**: the --json envelope carries `reason` (`identity-match` | `identity-mismatch` | `unknown-identity` | `fresh-install` | `forced`) and a skip envelope includes the matched artifact identity.
319
402
 
320
403
  ## 0.7.2
321
404
 
322
405
  ### Added
406
+
323
407
  - **`recipe-quality build`** — the single worker surface for producing `recipe-quality.json`: fronts @farmslot/agent-runtime's canonical builder (no reimplementation), validates against @farmslot/protocol `RecipeQualityArtifact` before writing (invalid input writes nothing, exit 5 with a teaching escape naming the invalid field). New production dependency: `@farmslot/agent-runtime`.
324
408
 
325
409
  ### Changed
410
+
326
411
  - `@farmslot/protocol` dependency raised to `^0.7.3` so one protocol version serves both the harness and the builder.
327
412
  - The packed-install contract test now executes `recipe-quality build` from the published layout, guarding the new dependency's packaging path.
328
413
 
329
414
  ## 0.7.1
330
415
 
331
416
  ### Added
417
+
332
418
  - **`doctor --expect-live`** — exit-coded runtime liveness for hooks and scripts: exit 0 only when the adapter runtime decision is `ready` (extension: watcher + CDP; mobile: metro + bridge; core: deps), non-zero with a teaching escape otherwise. Fails closed on every degraded path; reuses the doctor runtime probe, no duplicated checks.
333
419
 
334
420
  ### Fixed
421
+
335
422
  - **Per-command `--help` documents every implemented flag** (`call/run --list`, `--library`, `--action-manifest`, `logs --window`, `actions --action`, `launch --url`, `doctor --runtime-dir`, `stop --adapter`) and tab-completion matches — the CLI self-teaches its own surface. A contract test guards help/implementation parity.
336
423
  - **Injected runner shims resolve at run time** instead of a path baked at inject time: `MM_HARNESS_BIN` → global `mm-harness` on PATH → the recorded install-time path as last resort → teaching escape. A slot copied to or shared with another machine no longer points at a missing absolute path.
337
424
  - Removed two parsed-but-unused `launch` flags (`--yes`, `--json-stream`).
@@ -343,6 +430,7 @@ The "version people use": every remaining identified fix/improvement batched on
343
430
  (0.6.3, below). One themed commit per item; full contract suite green (macOS).
344
431
 
345
432
  ### Added
433
+
346
434
  - **`call --list` / `run --list`** — one scope-aware listing of everything invocable
347
435
  for the detected adapter: the fuzzy short-name action universe (each action's full
348
436
  name plus its unambiguous short name — what `call` accepts) and the runnable
@@ -356,6 +444,7 @@ The "version people use": every remaining identified fix/improvement batched on
356
444
  optional (absent binary → no row, never a doctor failure). Seam: `CAPTURE_HELPER_PATH`.
357
445
 
358
446
  ### Changed
447
+
359
448
  - **`fixtures set` is never silent** — an upfront intent line the moment work begins,
360
449
  plus the mobile setup-wallet leaf's own progress streamed live (heartbeat) during
361
450
  the 20-40s wallet build; stdout stays a clean `--json` envelope.
@@ -376,6 +465,7 @@ The "version people use": every remaining identified fix/improvement batched on
376
465
  `FARMSLOT_ROOT` a back-compat alias for one release.
377
466
 
378
467
  ### Fixed
468
+
379
469
  - **Extension `launch` teaches `yarn install --immutable` on missing/stale deps** —
380
470
  a pre-watcher deps-freshness probe fails fast (teaching only, never auto-installs)
381
471
  instead of letting webpack crash deep on stale `node_modules`; gated so a reusable
@@ -395,6 +485,7 @@ The "version people use": every remaining identified fix/improvement batched on
395
485
  - **Extension quick-tier webpack tail window** is regression-guarded (Metro parity).
396
486
 
397
487
  ### Internal
488
+
398
489
  - Dependabot: no high/critical advisories remain (existing `resolutions` cover them).
399
490
  - Boring-library swaps (pidtree, ajv) declined per the "delete hand-rolled code with a
400
491
  bug history" bar: the ps/lsof process detection is contract-tested and working, and
@@ -411,14 +502,17 @@ The "version people use": every remaining identified fix/improvement batched on
411
502
  ## 0.6.3
412
503
 
413
504
  ### Fixed
505
+
414
506
  - **Extension `launch` reuses a healthy live runtime instead of always clean-building** — quick `launch` / `launch --sidepanel` on an extension slot now probes for a reusable runtime (webpack watcher LISTENing on `WATCHER_PORT` + CDP `/json/version` reachable on `CDP_PORT` + dist not stale). When all hold it REATTACHES: re-snapshots `dist/chrome` into the loaded runtime-dist and reloads the extension in place over CDP (service worker + pages), re-asserts the side panel, and applies `--url` — against the existing slot Chrome profile, with no watcher stop, webpack cache clear, or Chrome kill/relaunch. Reuse is best-effort: a failed probe (watcher down, CDP unreachable, stale dist) falls through to the clean path, and `--build` is the explicit escape that always clean-builds + relaunches a fresh runtime (no probe) to force-recover a broken slot.
415
507
 
416
508
  ## 0.6.2 - 2026-07-06
417
509
 
418
510
  ### Added
511
+
419
512
  - **`-v` / `--version`** prints the mm-harness version.
420
513
 
421
514
  ### Fixed
515
+
422
516
  - **`logs` now streams live and shows the file path** — it captured output via spawnSync (never shown for a `tail -F` that doesn't exit), so `logs`/`logs --full` looked dead. New spawnInherit hands the terminal to the follow; the resolved log path + `tail -f`/`less +F` hints print upfront so you can page it yourself. Honors the test override seam.
423
517
  - **`debug` resolves the checkout's own port** (slot context → pool → formula) like launch/stop/doctor — bare `mm-harness debug` no longer 'fetch failed' against the wrong port.
424
518
 
@@ -427,10 +521,12 @@ The "version people use": every remaining identified fix/improvement batched on
427
521
  Fresh-install fixes found live on a published-0.6.0 install.
428
522
 
429
523
  ### Fixed
524
+
430
525
  - **Mobile `launch` no longer dies on empty `METAMASK_BUILD_TYPE`** — the quick-launch runs `expo start` directly (bypassing scripts/build.sh, which passes the build type as an arg); the fixture `.js.env` shipped `METAMASK_BUILD_TYPE=""`, which Metro's transform rejects (`Invalid METAMASK_BUILD_TYPE`). start-metro now defaults to the main dev client when unset, so already-installed slots launch without re-syncing fixtures.
431
526
  - **`doctor` no longer false-flags orphan Metros** — the detector matched any argv containing "metro" (caught the `tail -F metro.log` viewer) and reported the live, valid bundler as leaked. Now it matches only `expo start` bundlers and excludes the one serving the current port.
432
527
 
433
528
  ### Internal
529
+
434
530
  - Reap-checkout-metros test hardened against a SIGTERM timing flake (polls for exit).
435
531
 
436
532
  ## 0.6.0 - 2026-07-06
@@ -440,6 +536,7 @@ fresh-slot validation wave — every fix proven live on untouched -2 slots and
440
536
  independently cross-reviewed.
441
537
 
442
538
  ### Changed
539
+
443
540
  - **CLI decomposition + dist-only publish** — `src/cli.ts` is parse+dispatch only;
444
541
  handlers under `src/commands/<verb>.ts`; one commander parser; the npm tarball
445
542
  ships compiled `dist/` without `src/` and `tsx` is a devDependency (plain-node,
@@ -449,6 +546,7 @@ independently cross-reviewed.
449
546
  the same slot isolation launch has).
450
547
 
451
548
  ### Added
549
+
452
550
  - **`stop` reaps leaked Metro bundlers by checkout** (port-agnostic, path-
453
551
  boundary matched), `doctor` surfaces any orphan and teaches the reap.
454
552
  - **`launch --url <dapp>`** (extension) opens the dapp in the main tab beside the
@@ -457,6 +555,7 @@ independently cross-reviewed.
457
555
  - DX: run-mode + stale-`dist` warning in `bin`; overlay-delegation notice; docs/CODE-MAP.md.
458
556
 
459
557
  ### Fixed
558
+
460
559
  - `launch` targets the slot's own simulator (never the `booted` alias) and boots it;
461
560
  slot-port resolution layers rungs so a partial rung never nulls the port; provision
462
561
  resolves the simulator from the pool and boots before install.
@@ -470,6 +569,7 @@ independently cross-reviewed.
470
569
  Fresh-install hotfixes found in live validation of 0.5.0.
471
570
 
472
571
  ### Fixed
572
+
473
573
  - **First launch on a provisioned slot targets the slot's own simulator** — the mobile resolution ladder now reads the `runway-provision.json` baseline (simulator name/udid, watcher port, slot id) when `agentic-runtime.json` doesn't exist yet, and boots that simulator. Previously a provisioned-but-unprepared slot degraded to the simctl `booted` alias, missed the installed dev client, and guessed the wrong Metro port.
474
574
  - **`call` works from published installs** — library actions load harness helpers through a dist-preferring bridge instead of importing `src/index.ts` (node refuses to type-strip `.ts` under `node_modules`). The bridge is deliberately narrow (paths + run-recording) to avoid an import cycle through `adapters.ts`.
475
575
  - **deps-not-ready guidance matches the caller's state** — on a checkout without node_modules the taught command now leads with `yarn install --immutable` (yarn cannot run scripts at all on a bare checkout), and always ends with the launch re-run.
@@ -477,19 +577,26 @@ Fresh-install hotfixes found in live validation of 0.5.0.
477
577
  ## Unreleased
478
578
 
479
579
  ### Fixed
580
+
480
581
  - **`provision --json` adapter-detection failures emit the standard error envelope** — usage errors now match `doctor`/`launch` (envelope on stdout with `error.userAction`; stderr stays human-only). Previously `--json` callers got empty stdout.
481
582
 
482
583
  ### Changed
584
+
483
585
  - **CLI decomposition** — `src/cli.ts` is parse+dispatch only; handlers live under `src/commands/<verb>.ts`; `provision` routes through `src/commands/provision.ts` (not overlay harness dispatch); launch composition splits under `src/commands/launch/`; commander owns per-command `--help` (no hand-rolled interception).
484
586
  - **Published tarball is dist-only** — `files` no longer ships `src/`; `tsx` is a devDependency; `bin/mm-harness` and overlay leaves resolve `dist/*.js` (teaching error if a published install lacks `dist/`). Library actions keep importing `library/actions/harness-exports.mjs` (dist-preferring narrow bridge from 0.5.1); package exports remain for external consumers (`dist/index.js` published; `src/index.ts` when `--conditions=development` and dist is absent).
485
587
  - **resolve-farmslot-ports is TypeScript-first** — pool/context/formula port resolution lives in self-contained `adapters/shared/resolve-farmslot-ports-core.mjs` (works from runner and injected overlay copies); `src/adapters/resolve-farmslot-ports.ts` re-exports it for `slot-ports.ts`. `resolve-farmslot-ports.mjs` is the node leaf; `resolve-farmslot-ports.sh` remains a thin bash-compat wrapper for sourced callers.
486
588
 
487
589
  ## 0.5.0 - 2026-07-05
590
+
488
591
  Minor release: thin Runway provisioning (harness-owned slot provisioning with a shared artifact cache) plus compiled-dist packaging.
592
+
489
593
  ### Added
594
+
490
595
  - **`mm-harness provision runway`** — provisions a mobile slot with exactly: resolve the latest Runway (expo-dev-build) artifact, download it ONCE into a shared machine cache (`~/.cache/metamask-harness/runway/<runId>/`, sha256+size integrity, corrupt entries re-downloaded, never a slot failure), create the simulator if missing, install the dev client on it. Zero yarn/deps/Metro — those stay dispatch-time (lazy, presence-authoritative readiness unchanged). `install --runway` is the overlay-path equivalent (covered by its own contract test). Baseline recorded so doctor reports provisioned-but-deps-pending as a normal state.
491
596
  - **Compiled `dist/` for published installs** — the npm tarball ships esbuild-compiled JS; `npm i -g` installs run the dist entry (~0.21s cold vs ~0.37s tsx). Source checkouts without `dist/` keep the tsx dev path; `bin/mm-harness` prefers dist when present. Packaging contract test (pack → global install → dist entry runs).
597
+
492
598
  ### Changed
599
+
493
600
  - Runway provisioning callers (farm installer, mobile-farm `runway` prepare profile) delegate to the harness; the profile no longer runs a deps phase and a contract test enforces that.
494
601
 
495
602
  ## 0.4.0 - 2026-07-04
@@ -497,6 +604,7 @@ Minor release: thin Runway provisioning (harness-owned slot provisioning with a
497
604
  Minor release: the adapter-surface architecture plus the day's feature wave (0.3.6-0.3.9 shipped fixes as they were validated live; this cut marks the surface additions honestly).
498
605
 
499
606
  ### Added
607
+
500
608
  - **AdapterSurface** — one per-platform surface behind a common interface (`src/adapters/surface.ts`: resolveSlotPorts, runtimeStatus, devServer, logSources, hints); doctor/launch/stop/logs/debug/fixtures are surface-backed, so no verb hand-rolls platform branching. Contract: `docs/ADAPTER-SURFACE.md` (never-branch rule, add-a-platform checklist, migration table).
501
609
  - **Platform-adaptive `stop` on every adapter** — extension: kills this checkout's webpack watcher (pid file + orphan scan, slot-scoped) and closes its tail window; mobile: port-scoped Metro; core: headless teaching error. Idempotent, outcome-stating summaries.
502
610
  - **Doctor reports live runtime state on every adapter** — mobile Metro, extension webpack (via the extension readiness decision), core deps-only — one uniform shape, adapter-scoped colored context rows.
@@ -504,20 +612,24 @@ Minor release: the adapter-surface architecture plus the day's feature wave (0.3
504
612
  - **Context-aware examples and tier-visible flows** — `call` usage/help examples use the detected adapter's real manifest actions; `flows` prints a precedence legend decoding each source into personal > team > canonical.
505
613
 
506
614
  ### Changed
615
+
507
616
  - **BREAKING (agent contract)**: `doctor --json` `runtime.metro` (string) is now `runtime.devServer` `{label, status}` and is absent for core.
508
617
 
509
618
  ### Feature wave recap (0.3.6-0.3.9)
619
+
510
620
  - `mm-harness stop`; live-streamed mobile AND extension launches with upfront intent lines; presence-authoritative readiness (no setup-on-every-launch) with the dev-client running probe; slot-context-first device/port resolution; UTF-8 locale for pod spawns; DEV OVERRIDE banner + SLOT line + colored help; `mmdev-harness` completions; hermetic tmux-stubbed contract suite (now gating CI) + ESLint in `npm run check`; `docs/UX-PRINCIPLES.md` interaction contract.
511
621
 
512
622
  ## 0.3.9 - 2026-07-04
513
623
 
514
624
  ### Fixed
625
+
515
626
  - **Readiness is presence-authoritative** — a no-baseline mtime-only stale verdict no longer re-runs `yarn setup` inside every launch (warning instead; fingerprint-verified drift still installs), the deps baseline is recorded after any successful setup, and the `ready` decision probes that the dev client is actually running on the target device before doing nothing (`launch` launches).
516
627
  - **Slot-context-first resolution** — device/ports resolve from the checkout's `agentic-runtime.json` before pool/formula fallbacks, for mobile (simulator, Metro port) and extension (CDP + watcher ports, overwriting stale inherited env); naming formulas no longer hardcode pool prefixes.
517
628
  - **UTF-8 locale for pod-triggering spawns** — CocoaPods no longer dies on multibyte podspecs under locale-less orchestrator environments.
518
629
  - **Hermetic contract tests** — tmux/xcrun/adb are stubbed; the Metro log-window opens only in a run-owned session, never an unrelated attached one.
519
630
 
520
631
  ### Added
632
+
521
633
  - **`mm-harness stop`** — stop the port-scoped Metro this checkout owns and close its log-tail window; idempotent; teaching error on extension/core.
522
634
  - **Extension launch streams live** with an upfront intent line (ports named); mobile and extension long operations are never silent.
523
635
  - **Doctor reports live runtime state per platform** (deps currency, Metro on the slot's resolved port, dev-client presence) with adapter-scoped, colored runtime-context rows.
@@ -525,6 +637,7 @@ Minor release: the adapter-surface architecture plus the day's feature wave (0.3
525
637
  - **docs/UX-PRINCIPLES.md** — the CLI interaction contract (human surface rules; `--json` envelope purity is exempt and inviolable).
526
638
 
527
639
  ### Fixed
640
+
528
641
  - **Mobile preflight no longer re-runs `yarn setup` on every launch** — the deps readiness decision escalated an mtime hint to `stale` and ran the FULL `yarn setup` (submodules + inpage bridge + jetify + pods) inside the launch phase on every invocation in orchestrated (farmslot) contexts. `depsCheck` reports `stale` for a no-baseline checkout whenever a manifest is newer than the install markers; the orchestrator's git phase refreshes tracked-file mtimes on every sync while its deps phase leaves `node_modules` untouched, so "manifest newer than markers" is normal and not proof of drift — yet it duplicated the orchestrator's deps work and turned a fast preflight into 15+ minutes (the behavior that also looked like a hang before 0.3.8's streaming). **Presence is now authoritative:** without a recorded baseline an mtime-only `stale` is trusted as `current` (with a one-line stderr warning), so a churned-but-present checkout launches with zero `yarn` in both tiers. Genuine drift is still caught by the recorded-baseline fingerprint. `mm-harness launch` (quick tier / no `--build`) also dropped `preflightMode` before deciding, so the fast contract never took effect; it is threaded through now.
529
642
  - **Fast tier never installs; genuine gaps teach the orchestrator** — in the fast tier (quick launch / orchestrated), deps that genuinely need work (absent markers, an absent required package, a fingerprint drift, or Metro unable to resolve a module) return a teaching block naming the orchestrator deps/prepare phase (plus the standalone `yarn setup:expo` / `launch --build` remedies) instead of an implicit setup — deps are the orchestrator's contract. The standalone/full tier still installs.
530
643
  - **Deps baseline recorded the instant a setup leaf succeeds** — `prepareMobile` records the deps fingerprint baseline immediately after any successful `yarn-setup`, unconditionally (previously only a later `record: true` re-decide did, gated on the original decision being `install` and on `RECIPE_UP_INSTALL_ATTEMPTED`). Genuine-drift detection is fingerprint-based and self-sustaining after one good install.
@@ -533,6 +646,7 @@ Minor release: the adapter-surface architecture plus the day's feature wave (0.3
533
646
  - **Metro log-tail window no longer leaks into an unrelated tmux session** — running `start-metro` without an explicit session previously fell back to whichever tmux client was current or last attached. The viewer now opens a window only from `RECIPE_TMUX_SESSION` or the checkout runtime context and otherwise skips it; Metro still runs detached to the log. Extracted to `adapters/mobile/lib/tmux-viewer.sh` so the rule is unit-tested; contract tests stub tmux hermetically so no suite run can create a real window.
534
647
 
535
648
  ### Added
649
+
536
650
  - **Adapter surface — one seam for per-platform behavior** — `src/adapters/surface.ts` defines `AdapterSurface` + a `getAdapterSurface(adapter)` registry that owns the behavior commands used to hand-roll with `if (adapter === …)` ladders (slot-port resolution, read-only runtime status, dev-server describe/stop, log sources, and platform-phrased Next: hints), with thin `mobile`/`extension`/`core` implementations that delegate to the existing readiness/port plumbing (re-homed to `src/adapters/slot-ports.ts`). `doctor`, `launch`, `stop`, `logs`, `debug`, and `fixtures` now resolve platform behavior through the surface instead of branching. This closes the platform-blind class of bug (a platform handled in one command but forgotten in another). Documented in `docs/ADAPTER-SURFACE.md`; enforces `docs/UX-PRINCIPLES.md` principle 1.
537
651
  - **`doctor` reports a runtime section for every adapter** — the live runtime readiness line (decision + reasons, deps currency, dev server) now renders for extension (webpack watcher via `decideExtensionReadiness`) and core (deps-presence-only), not just mobile; the render is one uniform shape sourced from the surface.
538
652
  - **`mm-harness stop` is adaptive per platform** — mobile stops its port-scoped Metro and closes the log-tail window; extension stops the checkout's webpack watcher (pid file + orphan ps-scan, slot-scoped) and closes its tail window; core teaches the headless path. All idempotent (nothing-to-stop is success), with a uniform summary and `--json` envelope.
@@ -544,6 +658,7 @@ Minor release: the adapter-surface architecture plus the day's feature wave (0.3
544
658
  - **CLI help surface** — grouped/colored `mm-harness` help, a `DEV OVERRIDE ACTIVE` banner when `MM_HARNESS_BIN` is set, and a `SLOT` line when the checkout is a prepared slot; `mmdev-harness` completion alias.
545
659
 
546
660
  ### Changed
661
+
547
662
  - **`doctor --json` runtime dev-server shape is normalized** — the mobile-only `runtime.metro` string is replaced by a platform-labelled `runtime.devServer: { label, status }` (`metro` for mobile, `webpack` for extension; absent for headless core), so agents read one shape across platforms.
548
663
  - **`yarn check` runs ESLint** — flat-config ESLint (typescript-eslint recommended on `src/**/*.ts`, `@eslint/js` recommended on the `.mjs`/`.cjs` library) runs after typecheck and comment-hygiene. Dev-only (not in the published `files`); the baseline is zero-warning by config scoping.
549
664
  - **Deps install uses a scoped `setup:expo` variant when native isn't wanted** — `adapters/mobile/yarn-setup.sh` gains `--mode full|expo`. `expo` runs `yarn setup:expo --no-build-ios --no-build-android` (JS deps + prebuild, no native — native builds come from `open-device.sh`, never doubled); the full `yarn setup` runs only for a native build (`launch --build`). This is also the standalone Metro-only remedy taught by the fast-tier deps block.
@@ -552,47 +667,56 @@ Minor release: the adapter-surface architecture plus the day's feature wave (0.3
552
667
  ## 0.3.8 - 2026-07-04
553
668
 
554
669
  ### Fixed
670
+
555
671
  - **Mobile launch streams progress in `--json` mode** — mobile prepare leaves now run through `spawnScriptStreaming`, which tees the child's stdout+stderr to the parent's stderr live while stdout stays reserved for the JSON envelope. A multi-minute `mm-harness launch --json` is no longer silent (previously `spawnSync` buffered leaf output and suppressed it entirely under `--json`). Contract test asserts leaf progress reaches stderr, the envelope stays clean, and the streaming conversion preserves the exit-code contract.
556
672
 
557
673
  ## 0.3.7 - 2026-07-04
558
674
 
559
675
  ### Fixed
676
+
560
677
  - **Overlay install manifest no longer advertises the retired `launch.sh`/`live.sh`** — mobile launch/live orchestration is in-process now (`mm-harness launch`), and `install_v1_runner_assets` deletes `scripts/launch.sh` and `scripts/live.sh` on both install paths. The product-owned/metadata-only manifest already advertised `verify` only, but the overlay manifest's `runtimeHelpers` still listed `launch: scripts/launch.sh` and `live: scripts/live.sh` — paths the same install had just removed, a manifest that pointed at nonexistent files. The overlay manifest now advertises `verify` only, matching the product-owned path. A new manifest-honesty contract check in `tests/contract/mobile-inject.test.sh` asserts every advertised `runtimeHelpers` path exists on disk and that `launch`/`live` are not advertised, for both the product-owned and a full overlay install, so this cannot recur in either mode.
561
678
 
562
679
  ## 0.3.6 - 2026-07-04
563
680
 
564
681
  ### Fixed
682
+
565
683
  - **Install manifest carries target checkout identity (`targetRevision`)** — `adapters/mobile/inject.sh` stamps the target repo's `HEAD` (resolved once, up front, via `git rev-parse --verify HEAD`) and an explicit `installMode` into the mobile install manifest on both install paths (product-owned/metadata-only and overlay). Consumers (the farm recipe hook) can now gate their fast path on manifest identity matching the current checkout instead of manifest existence, closing the stale-manifest hole where a gitignored manifest survives a branch switch to a bridge-less checkout. Install now fails with teaching guidance when the target `HEAD` cannot be resolved (not-a-repo, unborn branch), instead of stamping an unusable `targetRevision`. Contract tests cover the identity stamp on both paths and the unborn-HEAD refusal.
566
684
 
567
685
  ## 0.3.5 - 2026-07-04
568
686
 
569
687
  ### Fixed
688
+
570
689
  - **IMP-24b: shell leaves run even when packed without the exec bit** — `yarn pack` (used by `yarn npm publish`) strips the executable bit off shipped `*.sh` leaves to `644`, even though git stores `100755` and `npm pack` preserves `755`. The 0.3.4 fix (git `100755` + an `npm pack` tarball test) was therefore insufficient: the published `yarn`-packed tarball still shipped `644` leaves and a fresh `npm i -g` hit `EACCES` on the first leaf. `spawnScript` now invokes shell leaves through `bash <leaf>` instead of exec'ing them directly, so the leaf's file mode is irrelevant (node leaves were already interpreter-invoked via `process.execPath`). Env overlays (`FORCE_COLOR`/`RECIPE_RUNTIME_DIR`), arg passing, cwd, JSON envelope, and exit codes are unchanged. New contract test `tests/contract/leaf-644-runs.test.sh` forces the device leaves to mode `644` and drives them through the real `mm-harness` spawn path, asserting they still run — the regression the `npm pack` tarball test could not catch.
571
690
 
572
691
  ### Changed
692
+
573
693
  - **Publish with `npm publish`, not `yarn npm publish`** — `npm publish` preserves the git `100755` mode on shipped `*.sh` leaves (defense-in-depth on top of the interpreter invocation above). Documented in `AGENTS.md`; git exec bits remain `100755`.
574
694
 
575
695
  ## 0.3.4 - 2026-07-04
576
696
 
577
697
  ### Fixed
698
+
578
699
  - **IMP-24: published tarball stripped exec bits from shell leaves** — the harness execs `adapters/**/*.sh` leaves directly, but six shipped leaves (`adapters/mobile/lib/metro-listener.sh`, `adapters/shared/activate-repo-node.sh`, `cli-ux.sh`, `harness-path.sh`, `hash-helpers.sh`, `resolve-farmslot-ports.sh`) were committed without the git exec bit (mode `100644`), so npm packed them `644`. A fresh `npm i -g` then hit `EACCES` on the first leaf. All shipped `*.sh` are now stored `100755` in git, so the published tarball packs them executable. Added contract test `tests/contract/packaging-exec-bits.test.sh` — it runs `npm pack` to produce the actual `.tgz`, then asserts via `tar tzvf` that every shipped `*.sh` entry has owner-execute set; fails with the offending path(s) if any is `644`, so this cannot silently regress.
579
700
  - **IMP-24: spawn failures are surfaced, not swallowed** — `spawnScript` returned on `result.error` (a leaf that is missing/`ENOENT` or not executable/`EACCES`) before its human-mode stderr forward, so a leaf that could not start produced a silent exit-1 (surfaced upstream as `MOBILE_PREPARE_FAILED`). The `result.error` branch now always writes a teaching diagnostic (`leaf could not start: <leaf> (<errno>)` + a `Next: reinstall mm-harness` hint) to stderr before returning. Covered by `tests/contract/spawn-error-surfaced.test.sh`.
580
701
 
581
702
  ## 0.3.3 - 2026-07-04
582
703
 
583
704
  ### Fixed
705
+
584
706
  - **IMP-23: per-run mobile runtime dir (`RECIPE_RUNTIME_DIR`)** — every on-disk mobile runtime file (`metro.log`, `metro.pid`, `metro.tmux`, `bridge-status.log`, `wallet-fixture.json`) now resolves from the shared runtime-dir resolver that honors `RECIPE_RUNTIME_DIR` instead of the hard-coded `<target>/temp/recipe/runtime`. `RECIPE_RUNTIME_DIR` must be a non-empty relative path under the target checkout — absolute values are rejected at validation. Shell (`recipe_runtime_dir` in `harness-path.sh`, used by `start-metro.sh`, `wait-for-bridge.sh`, `prewarm-bundle.sh`, and `bridge-runtime/setup-wallet.sh`) and TS (`recipeRuntimePath` in `runtime-decision.ts`) resolve the same location, so a run pointed at an isolated subdir (e.g. `temp/recipe/runtime-8081`) writes and reads nothing under the default. Two harness jobs sharing one checkout each set `RECIPE_RUNTIME_DIR` to a distinct relative subdir for full runtime isolation.
585
707
  - **IMP-23: port-scoped Metro guard** — the Metro detect/kill/restart helpers are extracted into `adapters/mobile/lib/metro-listener.sh` with an explicit invariant: discovery, inspection, and signalling key off the managed watcher-port only (`lsof -iTCP:<port> -sTCP:LISTEN`). A Metro listening on any other port is never selected, never has its cmdline read, and is never signalled, so a run managing one port cannot detect or kill a concurrent Metro on another port in the same checkout.
586
708
 
587
709
  ## 0.3.2 - 2026-07-04
588
710
 
589
711
  ### Fixed
712
+
590
713
  - **IMP-21: FORCE_COLOR-safe pod install** — the mobile pod-triggering leaves (`yarn-setup.sh` running `yarn setup`, and `open-device.sh` running the native `yarn start:ios|android` build) now run with `FORCE_COLOR=0` / `NO_COLOR=1`. VisionCamera's podspec probes `node --print require.resolve('react-native-worklets-core')` and treats any output other than the exact string `undefined` as "found"; an inherited `FORCE_COLOR` made node emit a colorized `undefined`, enabling FrameProcessors and hard-failing on the missing worklets pod. Setup/native builds are now reliable from a `FORCE_COLOR` shell.
591
714
  - **IMP-22: `--watcher-port` beats `.js.env WATCHER_PORT`** — `verify.sh` port resolution now honors an explicit `WATCHER_PORT` in the process env (which carries `mm-harness launch --watcher-port N`) over the target's `.js.env`. Precedence is flag > process env > `.js.env` > `8081` default, so a run can be pointed at an alternate Metro/CDP port without editing a shared slot's `.js.env`.
592
715
 
593
716
  ## 0.3.1 - 2026-07-03
594
717
 
595
718
  ### Fixed
719
+
596
720
  - **Customization compose-by-ref**: recipes with `{action:"call",ref:"<lib>.<flow>"}` + `--library <lib>=<dir>` now pass pre-run schema validation AND execute at runtime. `validateRecipeAdapterAware` threads `externalFlowIds` into both `validateRecipeDocument` and `validateRecipeWithManifest`; library resolution also runs at `run` time.
597
721
  - **IMP-14: stale metro.log guard** — if `metro.log` is older than the install markers (`node_modules/.yarn-state.yml`, `.yarn/install-state.gz`), it is treated as absent so a stale log cannot drive a false native-module-mismatch decision at launch.
598
722
  - **IMP-15: stale overlay shim auto-repair (two modes)** — `ensureOverlay` detects (1) a `.runner-source` pointing to a nonexistent runner dir, and (2) a `.runner-source` whose dir exists but whose `bin/mm-harness` was removed. Both re-install automatically before launch.
@@ -602,9 +726,11 @@ Minor release: the adapter-surface architecture plus the day's feature wave (0.3
602
726
  ## 0.3.0 - 2026-07-03
603
727
 
604
728
  ### Changed
729
+
605
730
  - Porcelain layer eliminated: the legacy `mm-recipe`/`mme-recipe`/`metamask-recipe` bins are gone; launch/logs/debug/fixtures compose the adapter scripts directly in TypeScript. Repository reorganized into `src/` (all TS), `adapters/` (per-platform runtime glue), `library/` (recipe/action/flow vocabulary).
606
731
 
607
732
  ### Fixed
733
+
608
734
  - Extension `launch` opens the app directly (composes `live.sh`) instead of a retired command.
609
735
  - Mobile `cleanup` resilient to a stale runner-source path.
610
736
  - Core run teaches the correct next step when no account is set.
@@ -907,11 +1033,13 @@ Minor release: the adapter-surface architecture plus the day's feature wave (0.3
907
1033
  - `checklist mark ... complete` now teaches the exact compact quality input, canonical builder command, and coverage artifact when a recipe activates those terminal requirements, instead of forcing agents to reverse-engineer the artifact validator.
908
1034
 
909
1035
  ### Added
1036
+
910
1037
  - Checkout-scoped operation locking prevents concurrent launch, fixture mutation, and recipe execution in one repository while allowing other checkouts/slots to run independently; stale locks from exited processes are reclaimed.
911
1038
  - `doctor --fix` creates an ignored, checkout-scoped `agentic-runtime.json` when absent, with stable claimed local ports for ordinary standalone clones while preserving explicit or Farmslot-provided slot resources.
912
1039
  - `fixtures init --from <path>|--dev` initializes the canonical checkout wallet fixture explicitly; `--dev` is a disposable public test wallet and is never suitable for real funds.
913
1040
 
914
1041
  ### Fixed
1042
+
915
1043
  - Missing checkout targets now return the structured usage envelope before lock acquisition in `run` and `doctor`; recovery bounds distinguish a consumed successful repair, adapter helpers preserve explicit runtime/device pins, checklist-only runtime resolution is lazy, and fixture finalization is time-bounded.
916
1044
  - Checkout contention now uses one bounded JSON contract across `call`, `launch`, `doctor --fix`, `fixtures`, and `run`: exit 4 with `SANDBOX_BUSY` and an actionable lock path.
917
1045
  - Extension loaded-snapshot checks now use size/mtime comparison instead of full checksums, fall back to an in-process comparison when `rsync` is unavailable, and report comparison failures without forcing a misleading relaunch.
@@ -949,23 +1077,27 @@ Minor release: the adapter-surface architecture plus the day's feature wave (0.3
949
1077
  ## 0.14.7 - 2026-07-10
950
1078
 
951
1079
  ### Changed
1080
+
952
1081
  - `doctor --print-ready` now implies the exit-coded live probe — Farmslot `health_check` hooks need only `--print-ready`, not `--expect-live --print-ready`.
953
1082
  - Mobile `--print-ready` uses bridge `walletState` (unlocked + live bridge), not React Navigation route names; mobile `ready_indicator` is `OK`.
954
1083
  - Removed `metamask-recipe` install symlinks from adapter inject paths (no compat aliases).
955
1084
  - `doctor --print-ready` rejects `--json` (stdout is reserved for the Farmslot indicator line).
956
1085
 
957
1086
  ### Fixed
1087
+
958
1088
  - Mobile `surface.runtimeStatus` infers Android from `ADB_SERIAL` / `ANDROID_SERIAL` when present.
959
1089
 
960
1090
  ## 0.14.6 - 2026-07-10
961
1091
 
962
1092
  ### Added
1093
+
963
1094
  - `mm-harness doctor --print-ready` — Farmslot `health_check` mode: prints `health.ready_indicator` on stdout (`extension`/`mobile`: `OK`; `core`: `ready`).
964
1095
  - `doctor --expect-live` — exit-coded liveness gate without indicator output (prepare recovery and other pass/fail callers).
965
1096
 
966
1097
  ## 0.14.5 - 2026-07-10
967
1098
 
968
1099
  ### Fixed
1100
+
969
1101
  - Extension `ensure-browser` closes disposable New Tab / blank / extensions pages after reopen, matching reattach/ensure-ready hygiene.
970
1102
  - Extension slot browser titles are stamped through one shared helper (`adapters/extension/lib/slot-title.cjs`) with a persistent `MutationObserver`, and re-applied after `ensureExtensionReady` open/prune/reopen so relaunch and recipe paths keep `<slot-id> — MetaMask` instead of a bare MetaMask window.
971
1103
  - Extension slot-title contract test is hermetic (local CDP/WebSocket stub + fake DOM); Playwright and CDP stamps share one function source so they cannot drift.
@@ -974,11 +1106,13 @@ Minor release: the adapter-surface architecture plus the day's feature wave (0.3
974
1106
  ## 0.14.4 - 2026-07-09
975
1107
 
976
1108
  ### Fixed
1109
+
977
1110
  - Include the new extension viewer cleanup scripts in the adapter manifest so published packages pass adapter-surface validation.
978
1111
 
979
1112
  ## 0.14.3 - 2026-07-09
980
1113
 
981
1114
  ### Fixed
1115
+
982
1116
  - Extension `status` and `doctor` now accept a sidepanel-only live runtime, matching `run`/`call` health checks instead of requiring a fullscreen `home.html` tab after `launch --sidepanel`.
983
1117
  - Extension sidepanel launch waits longer for Chrome to expose the `sidepanel.html` CDP target, avoiding false failures after Chrome accepts the open request.
984
1118
  - Extension tmux viewers are opened only in the run-owned tmux session and are cleaned by `mm-harness stop`, preventing stale `webpack-*`/`console-*` windows from leaking across slots.
@@ -987,15 +1121,18 @@ Minor release: the adapter-surface architecture plus the day's feature wave (0.3
987
1121
  ## 0.14.2 - 2026-07-09
988
1122
 
989
1123
  ### Fixed
1124
+
990
1125
  - Require `@farmslot/recipe-harness` `^0.4.3` so runtime dependency readiness trusts install markers newer than an old recorded baseline and stops repeatedly prompting managed slots to reinstall fresh dependencies.
991
1126
 
992
1127
  ## 0.14.1 - 2026-07-09
993
1128
 
994
1129
  ### Added
1130
+
995
1131
  - `mm-harness check diff` runs bounded repo-local validation for the active git diff, writing `validation-summary.json`/`.md` plus per-check logs. The fast profile runs changed-file ESLint, Prettier, and changed test files; `--profile full` also runs the repo typecheck when available. The default base follows the active PR base or repo remote HEAD before falling back to common branch names.
996
1132
  - `mm-harness run` writes a concise human report (`report.md`) and prints a colored step summary for every adapter, so successful Core/Mobile/Extension recipe runs are scannable without opening raw trace files.
997
1133
 
998
1134
  ### Fixed
1135
+
999
1136
  - Mobile slot targeting now refuses a platform launch/run/call when `--platform ios|android` conflicts with the configured default target, preventing an iOS-pinned slot from silently stealing a shared Android device; `status` and `doctor` scope device lists to the configured target by default and `status` reports other reachable bridges as additional targets that can be driven with `--device`.
1000
1137
  - Mobile `status` bases `Next:` on the configured default target only, avoids assigning an additional Android bridge to a same-platform selected device, and reports stale Android reverse ports when the selected device is not connected to the slot Metro.
1001
1138
  - Mobile `doctor` now reports the same additional reachable targets as `status`, so diagnostics and the home view agree on which devices can be driven with `--device`.
@@ -1012,11 +1149,13 @@ Minor release: the adapter-surface architecture plus the day's feature wave (0.3
1012
1149
  ## 0.14.0 - 2026-07-08
1013
1150
 
1014
1151
  ### Added
1152
+
1015
1153
  - `mm-harness call` accepts action fields as direct `key=value` shorthand in addition to repeated `--arg k=v`, so commands like `mm-harness call ui.navigate page=perps` now match the examples agents naturally write.
1016
1154
  - Packaged recipes are grouped by domain under `library/recipes/<domain>/` and still resolve by dotted names (`perps.smoke`, `runner.smoke`) with legacy aliases for the former flat names.
1017
1155
  - Packaged `perps.smoke` recipes for mobile, extension, and core provide an explicit non-mutating Perps domain smoke. The existing bare `smoke` alias remains a runner/transport smoke for compatibility.
1018
1156
 
1019
1157
  ### Fixed
1158
+
1020
1159
  - The top-level help SLOT banner now labels the slot dev-server port by adapter, so extension checkouts show `webpack :<port>` instead of the mobile-only `metro :<port>`.
1021
1160
  - Hidden internal runtime commands remain executable through the installed `mm-harness` bin, fixing Extension verify warnings where `runtime-decision` returned `{}` because the wrapper blocked its own shipped helper.
1022
1161
  - Extension launch/readiness now stamps the live home tab title with the Farmslot slot id after MetaMask has settled, so headed browser windows identify their owning slot.
@@ -1029,11 +1168,13 @@ Minor release: the adapter-surface architecture plus the day's feature wave (0.3
1029
1168
  ## 0.13.0 - 2026-07-08
1030
1169
 
1031
1170
  ### Added
1171
+
1032
1172
  - Typed cdp-bridge failure codes (`NO_TARGET`, `CDP_TIMEOUT`, `WS_CLOSED`, `METRO_UNREACHABLE`): the bridge classifies a failure at its source and reports it three ways a caller can recover — an `ERROR[<CODE>]:` stderr marker, a code-specific exit status (10–13), and a `Next:` teaching line for the caller's actual situation. `bridge.mjs` attaches the code to the error it throws, and `app.hud` lifecycle-skip classification (`isMobileHudLifecycleSkip`) branches on the code first, keeping the substring needles only as a fallback for output from a bridge that predates the codes. The needle fallback is now case-insensitive, closing a gap where target-discovery's `Pinned Android device …` (capital A) missed a lowercase needle.
1033
1173
  - Console-forwarder now expands object/array console arguments in `metro.log` from the Hermes-supplied inline `preview` (e.g. `{ symbol: "BTC", px: 42 }` instead of the literal `Object`), bounded one level deep and rendered synchronously so the stream never blocks; objects without a preview degrade to their description.
1034
1174
  - `mm-harness call <action> --help` now renders the named action's own field schema (name, type, required, description, example — from the action manifest) above the generic call flags, instead of printing only the generic call help. Fuzzy short-name resolves like `call` (an ambiguous prefix shows every match); an unresolvable name falls back to the generic help plus a pointer to `mm-harness actions`. `run <recipe> --help` is unchanged (recipe-specific help is a separate, larger surface — recipes are not in the action manifest).
1035
1175
 
1036
1176
  ### Fixed
1177
+
1037
1178
  - Runner construction no longer fails with `Manifest action app.lifecycle has no registered adapter` when a manifest declaring `app.lifecycle` is used with a non-mobile adapter (e.g. `call --adapter core --action-manifest library/manifests/mobile.action-manifest.json`): lifecycle adapter registration is manifest-driven instead of gated on the mobile adapter. Executing the action outside a mobile run still fails explicitly at target resolution.
1038
1179
  - Mobile run teardown always clears any HUD step left painted on-device, so a failed run no longer strands a FAIL banner for the next run. Best-effort and bounded: a down/transitioning bridge simply has nothing to clear and never masks the run's real outcome.
1039
1180
  - start-metro quick-launch now applies the dev Sentry DSN remap (`MM_SENTRY_DSN` defaults to `MM_SENTRY_DSN_DEV` when unset) at both launch paths. The quick-launch runs `expo start` directly and bypasses `scripts/build.sh`, which normally performs this remap, so without it Sentry never initialized in quick-launched dev clients.
@@ -1042,9 +1183,11 @@ Minor release: the adapter-surface architecture plus the day's feature wave (0.3
1042
1183
  ## 0.12.0 - 2026-07-08
1043
1184
 
1044
1185
  ### Added
1186
+
1045
1187
  - Mobile console-forwarder: restores the device console (incl. DevLogger) in `metro.log` on RN 0.79+/Bridgeless via persistent CDP streaming (`Runtime.consoleAPICalled`) — one session per device JS-runtime page (shared target-discovery ranking, never the native C++ page), buffer replay + persisted last-seen cursor (timestamp + same-stamp texts) deduping across reconnects/app reloads, auto-started by `start-metro`; opt out with `METAMASK_RECIPE_CONSOLE_FORWARD=0`. Grep `[console:` for device lines. Zero-dependency transport (built-in Node 22+ WebSocket, like the CDP bridge client). Yields to interactive React Native DevTools: a session evicted by another debugger with no bridge lock present backs off for 5 minutes instead of evicting the human every discovery tick. Idles at the slow poll cadence while Metro is down; flushes pending lines and dedupe state synchronously on SIGTERM/SIGINT.
1046
1188
 
1047
1189
  ### Fixed
1190
+
1048
1191
  - `app.hud` no longer aborts recipes on `CDP message timeout` / `WebSocket closed` during dev-client lifecycle transitions (0.11.0 regression that failed every warm-start run at `foreground-app`/`ensure-unlocked`). Skips are loud: the reason is written to stderr and recorded in the action result, so a masked real failure remains visible.
1049
1192
  - cdp-bridge takes a runtime lock (`cdp-bridge.lock`) so the console-forwarder yields the single debugger slot during bridge commands — prevents mutual eviction and lost `Runtime.evaluate` replies. The forwarder honors the lock while the pid inside it is alive (`kill -0`), so long commands (wallet setup runs `CDP_TIMEOUT=120000`) are never evicted mid-flight while a crashed/killed bridge releases immediately; the bridge releases on SIGTERM/SIGINT, only unlinks a lock it still owns (last-writer-wins under overlap), and takes no lock for `--help`/unknown commands.
1050
1193
  - `stop-metro` and mobile `cleanup` stop the console-forwarder (recorded pid + checkout-scoped sweep), so no orphan forwarder outlives Metro or fights a later one for the debugger slot.
@@ -1053,14 +1196,17 @@ Minor release: the adapter-surface architecture plus the day's feature wave (0.3
1053
1196
  ## 0.11.0 - 2026-07-07
1054
1197
 
1055
1198
  ### Added
1199
+
1056
1200
  - Mobile recipes can now use the standard outer `app.lifecycle` action for deterministic performance start states without rebuilding (Android background/foreground/terminate/restart, iOS simulator launch/terminate/restart through the shared Farmslot lifecycle adapter).
1057
1201
  - `fixtures set --device <serial|udid|name>` now pins mobile fixture sync to the requested device using the same targeting rules as `run`, `call`, and `doctor`.
1058
1202
  - New lifecycle-controlled measured flows in the packaged library: `app.lifecycle.android-smoke`, `perps.performance.warm-start`, `perps.performance.background-resume`, `perps.performance.cold-start`; performance recipes now keep lifecycle/unlock in `setup[]`/`startState` so measured nodes carry only CUF timings.
1059
1203
 
1060
1204
  ### Changed
1205
+
1061
1206
  - `@farmslot/recipe-harness` dependency raised to `^0.4.0` (ships the `app.lifecycle` adapter — the `FARMSLOT_ROOT` local-source fallback is no longer needed at runtime).
1062
1207
 
1063
1208
  ### Fixed
1209
+
1064
1210
  - `launch ios --device <udid|name>` again accepts a shutdown simulator target: launch preserves the requested simulator identity and lets `open-device.sh` boot it instead of requiring it to appear in the booted-device list first.
1065
1211
  - iOS UDID pins are exported as both `SIM_UDID` and simulator name where available, so downstream simulator tooling can use the stable name while preserving the exact pin.
1066
1212
  - `metamask.wallet.ensure_unlocked` now waits for the pinned agentic bridge target, tolerates transient status drops during the unlocked stability check, and avoids the old full password-unlock fallback for brief bridge gaps.
@@ -1072,17 +1218,20 @@ Minor release: the adapter-surface architecture plus the day's feature wave (0.3
1072
1218
  ## 0.10.0 - 2026-07-07
1073
1219
 
1074
1220
  ### Added
1221
+
1075
1222
  - **Canonical `perps.performance` measured flow** (`library/recipes/perps/performance.mobile.recipe.json`): unlock → open the Perps market list → read live state → open a market detail — one node per user-visible step with stable node names, so the per-node `duration`s in `trace.json` are the timings you monitor. Run pinned: `mm-harness run perps.performance --device <serial> --heal off`. Device-proven end-to-end on a physical Pixel.
1076
1223
  - **`run <name>` resolves personal/team recipe libraries.** Previously only the packaged library was probed by name; custom recipes ran by path. Sources resolve in library-precedence order (personal > team shadow the packaged canonical — a same-named personal recipe wins), path-shaped args never probe libraries, and a miss teaches which sources were searched. Zero-flag default: `$FARMSLOT_HOME/recipe-library`.
1077
1224
  - **Custom-library walkthrough** ("Your own measured flow" in `docs/recipe-libraries.md`): a peer engineer scaffolds a personal library, copies the canonical flow, retargets the nodes to their journey, and runs it by name. Every step is executed by the `perps.performance-recipe` contract test, so the doc cannot drift from reality.
1078
1225
  - **`call` declared on mobile and extension manifests** (was core-only) with self-discovery metadata — personal `flows/` segments are now usable from mobile/extension recipes. Honest limitation documented: flows carry no adapter dimension in the protocol yet, so a cross-adapter `call` fails at live-run rather than plan time.
1079
1226
 
1080
1227
  ### Fixed
1228
+
1081
1229
  - **A `--device` pin wins target selection end-to-end on dual-platform slots.** Three independently sufficient holes let a pinned android run drive the iOS simulator (observed live): the discovery simulator filter ran before the android pin and the slot's ambient `IOS_SIMULATOR` captured the candidate set; pins were only enforced when more than one candidate existed (a single WRONG candidate was silently accepted); and the wallet actions' status-entry selectors checked the ambient simulator identity first while never matching serials against Metro device names. Fixed at every layer with live-repro contract cases; an unmatchable pin fails fast listing the Metro candidates.
1082
1230
 
1083
1231
  ## 0.9.1 - 2026-07-07
1084
1232
 
1085
1233
  ### Fixed
1234
+
1086
1235
  - **Metro survives the launching shell.** `start-metro.sh` spawned Metro without SIGHUP protection, so closing the launching tmux window/shell killed it, leaving a stale `metro.pid` and hanging every subsequent bridge/fixtures call. Metro now starts under `nohup` (argv spawn, no `bash -c` string interpolation), and a stale `metro.pid` naming a dead process is detected and cleaned before start.
1087
1236
  - **`--device <adb serial>` reaches CDP target selection.** The runner's ambiguity gate resolved the serial, but target discovery compared it against Metro's `deviceName` ("Pixel 6 - 16 - API 36") — never a serial — so with an iOS simulator also attached, a recipe pinned to the physical Android device could silently drive the simulator. The serial is now mapped to the Metro identity via `adb -s <serial> shell getprop ro.product.model` with model-prefix matching (scoped to non-simulator targets); an unmatchable or ambiguous pin (two same-model devices) fails fast listing every Metro `/json/list` candidate instead of silently picking one. `--device <serial>` remains the only thing users pass.
1088
1237
  - **Bridge commands always emit valid JSON.** `get-route` printed the literal string `undefined` when the route was transiently unavailable mid-navigation, so `bridgeCommand()` threw on parse and `waitForRoute()` aborted instead of polling. `get-route`/`navigate`/`go-back` now normalise a missing route to `null`, `bridgeCommand()` treats `''`/`undefined` stdout as not-settled-yet only for transient-legitimate commands, and `waitForRoute()` polls through `null` until timeout — the timeout error carries the expected route, last parsed route, last bridge reply and the device pin.
@@ -1091,12 +1240,14 @@ Minor release: the adapter-surface architecture plus the day's feature wave (0.3
1091
1240
  ## 0.9.0 - 2026-07-07
1092
1241
 
1093
1242
  ### Added
1243
+
1094
1244
  - **`status` auto-probes live app state per device** — the `yarn a:status` successor. Static info prints instantly; within a strict 2s window each device line is enriched with `screen=` (active route via the `__AGENTIC__` bridge, Route objects normalised), `wallet=locked|unlocked|onboarding`, `account=<label> (0x1234…abcd)` and `fixture=READY|missing`. `--fast` skips all probes (guaranteed-instant for scripts); `--json` carries the enriched `devices[]` in one envelope. All RN targets attached to the checkout's Metro are probed and matched per device (exact id → exact name → platform-uniqueness → leftover 1:1), so android + ios side by side on one Metro both report. Honest degradation: `no-bridge` (nothing matched/answering) vs `bridge-absent` (target attached but the installed build predates `__AGENTIC__`, rendered with a rebuild hint) — surfacing pre-bridge builds required the target discovery probe to become three-state (agentic/responsive/unreachable) instead of silently dropping them.
1095
1245
  - **Every action now carries self-discovery metadata.** `action_metadata` (description + example recipe node, derived from the real handler implementations) covers all official and custom actions across the mobile, extension and core manifests — previously `app.status`, `cdp.target` and all 16 `metamask.wallet.*`/`metamask.perps.*` actions were bare names to agents. A new contract guard enforces coverage (including example structure: `node.action` must match), and locks mobile ↔ extension action-set parity with an explicit platform-only allowlist.
1096
1246
 
1097
1247
  ## 0.8.0 - 2026-07-07
1098
1248
 
1099
1249
  ### Added
1250
+
1100
1251
  - **`run <name>` — the recipe positional resolves packaged-library recipe names.** An existing file path (absolute or cwd-relative) wins; otherwise the arg is a library recipe name, adapter-scoped first (`run smoke` on mobile → `library/recipes/runner/smoke.mobile.recipe.json`), then adapterless (`run perps.lifecycle` → `library/recipes/perps/lifecycle.recipe.json`), then the exact library filename. A miss teaches `RECIPE_NOT_FOUND` naming the library recipes available for the adapter — no more `$LIB`-style path prefixes in docs or demo scripts. A directory shadowing a library name never wins, and path-shaped args (containing a separator) never fall through to the library.
1101
1252
  - **`--device <udid|serial|name>` — first-class mobile device targeting on `run`, `call`, and `doctor`.** Resolves against connected devices (adb + booted simulators; exact id first, then exact name with an ambiguity teaching error) and sets the same env `launch --device` does. Mobile `run`/`call` **without** `--device` while more than one targetable device is connected (across android + ios; android state `device`, iOS `Booted`) fail fast listing the devices and the `--device` hint, so a recipe never lands on the wrong device silently. `run --plan`/`--list` are exempt; extension/core teach. Errors honor `--json` with structured envelopes.
1102
1253
  - **`status` gains `devices[]`** (additive): `{platform, id, name, state, selected}` for connected android devices and booted iOS simulators on mobile checkouts — the `yarn a:status` successor; `selected` reflects the current env pinning.
@@ -1104,48 +1255,58 @@ Minor release: the adapter-surface architecture plus the day's feature wave (0.3
1104
1255
  ## 0.7.5 - 2026-07-07
1105
1256
 
1106
1257
  ### Fixed
1258
+
1107
1259
  - **`launch --verify` converges duplicate extension home tabs instead of failing prepare forever.** A second bare `home.html` tab can appear beside the operator's slot-titled one outside the harness's control (the extension opens its own tab after the build-freeze reload), and the live verify health gate requires exactly one home tab — but nothing in launch/verify ever collapsed duplicates (`ensureExtensionReady` was only reachable via the retired `ensure-ready` verb), so every preflight retry recounted two tabs and failed. `launch --verify` (extension, with a CDP port) now runs the convergence before the live verify phase.
1108
1260
  - **Convergence keeps the operator's tab.** With multiple home tabs, `ensureExtensionReady` now prunes the extras keeping the preferred tab — attached (another client is driving it) over custom slot title (`<slot-id> — MetaMask`, how operators tell which agent owns a browser) over first listed — instead of closing all and reopening fresh, which discarded the slot title. The deterministic close-all + fresh-open remains as a one-shot fallback when the kept tab fails the health check, and a failed re-listing during the fallback reports `cdp-unreachable` instead of a stale tab count.
1109
1261
 
1110
1262
  ### Changed
1263
+
1111
1264
  - **`EnsureReadyAction` gains `pruned`** — extras were closed keeping the preferred tab; `reopened` now specifically means the pruned keeper failed the health check and one fresh tab was reopened.
1112
1265
 
1113
1266
  ## 0.7.4 - 2026-07-07
1114
1267
 
1115
1268
  ### Added
1269
+
1116
1270
  - **`fixtures generate`** — renders the extension wallet `fixture-state.json` from a wallet fixture by fronting `adapters/extension/wallet-fixture-state.cjs generate` (no reimplementation). Extension-only (mobile/core teach); requires `--fixture <wallet-fixture.json>` and `--out <fixture-state.json>`, with a `--json` machine envelope and teaching escapes on missing inputs or a non-extension adapter. This is the public surface farm packs call instead of reaching into the internal leaf, so the Extension pack's `setup/generate-fixture.cjs` resolver can be replaced by a one-line `mm-harness fixtures generate` hook.
1117
1271
  - **`fixtures finalize`** — seeds account labels/selection into the **live** extension over CDP and validates import parity, by fronting `adapters/extension/wallet-fixture-state.cjs seed-cdp` (no reimplementation). Extension-only, post-launch (mobile/core teach); requires `--fixture`, `--state`, `--cdp-port`, `--extension-dir` (optional `--extension-id-file` / `--out`), with a pure `--json` envelope (leaf progress on stderr), the shared `LEAF_MISSING` pre-check, and teaching escapes on missing flags or a non-extension adapter. This is the public surface for the Extension pack's `setup/preflight.sh` account-label finalization, retiring its last `--resolve-script` reach-in into the internal leaf.
1118
1272
 
1119
1273
  ### Fixed
1274
+
1120
1275
  - **`fixtures generate` teaches on a missing leaf instead of leaking a node error.** When the `wallet-fixture-state.cjs` leaf is absent (a corrupt packed install), `generate` now pre-checks the leaf on disk and fails with a one-sentence teaching error plus a `Next:` reinstall command (and a clean `--json` `LEAF_MISSING` envelope), instead of surfacing node's opaque "Cannot find module".
1121
1276
  - **Extension readiness no longer spawns a duplicate home tab on a transient CDP hiccup.** `ensureExtensionReady` treated any `/json/list` failure as an empty tab set, so a list that transiently failed mid-prepare (unlock → home rerender) looked like "no home tab" and opened a second one next to the healthy tab — the following health check then found two targets and failed prepare. `/json/list` now distinguishes "CDP answered: N targets" from "CDP unreachable" (retried a few times), returns `reasonCode: 'cdp-unreachable'` without touching any tab when the state is unknown, confirms a zero count with a second listing before opening, and converges over a few passes so a wrong action is corrected within the call instead of tripping the health check.
1122
1277
  - **`readiness` no longer opens a duplicate home tab when the existing one is attached.** `findPageTarget` required `webSocketDebuggerUrl`, but Chrome omits that field for any target another client is attached to — during prepare the fixture finalizer / smoke recipe / CDP evals attach to the healthy home tab, so it listed without a wsUrl, looked absent, and `readiness` opened a second `home.html` that then tripped the exactly-one-home health gate. Page existence is now decided by URL in any attach state; the websocket only gates UI inspection (a present-but-attached tab is reported `pagePresent: true`, `pageInspected: false` and left alone instead of duplicated). URL matching also tolerates the `home.html#/` router rewrite and query suffixes. New additive report fields: `pagePresent`, `pageInspected`.
1123
1278
 
1124
1279
  ### Changed
1280
+
1125
1281
  - **`install --adapter core` is documented and contract-locked as the public core-runner install surface for farm packs.** It already fronts `adapters/core/inject.sh` and produces the runner delegate at `<harness>/core/runner/bin/mm-harness`; a new CLI-level contract test (`tests/contract/core-install-cli.test.sh`) pins the happy path, `--json` envelope purity, and the adapter-detection teaching escape. Packs call `mm-harness install --adapter core --target <repo>` (inheriting `RECIPE_HARNESS_ROOT` / `FARMSLOT_ROOT`) instead of cloning a runner checkout and invoking the internal leaf directly — no separate top-level command is minted.
1126
1282
  - **`EnsureReadyResult` gains a machine-readable `action` field** (`none` | `opened` | `reopened` | `skipped`) stating the tab decision the call took, so a consumer distinguishes "opened one because a zero was confirmed" from "touched nothing because CDP state was unknown" straight from the JSON envelope.
1127
1283
 
1128
1284
  ## 0.7.3
1129
1285
 
1130
1286
  ### Fixed
1287
+
1131
1288
  - **`provision runway` gates its install-skip on artifact identity.** The baseline now records the installed artifact identity (run id, branch, digest) at install time and a skip preserves it; provisioning skips ONLY when the recorded identity matches the requested (or probe-resolved) run — a different or unknown installed app is reinstalled from the cache instead of being silently kept. Previously any installed MetaMask.app suppressed the install, so a stale binary could be served against a newer JS bundle.
1132
1289
  - **Provision decisions are machine-readable**: the --json envelope carries `reason` (`identity-match` | `identity-mismatch` | `unknown-identity` | `fresh-install` | `forced`) and a skip envelope includes the matched artifact identity.
1133
1290
 
1134
1291
  ## 0.7.2
1135
1292
 
1136
1293
  ### Added
1294
+
1137
1295
  - **`recipe-quality build`** — the single worker surface for producing `recipe-quality.json`: fronts @farmslot/agent-runtime's canonical builder (no reimplementation), validates against @farmslot/protocol `RecipeQualityArtifact` before writing (invalid input writes nothing, exit 5 with a teaching escape naming the invalid field). New production dependency: `@farmslot/agent-runtime`.
1138
1296
 
1139
1297
  ### Changed
1298
+
1140
1299
  - `@farmslot/protocol` dependency raised to `^0.7.3` so one protocol version serves both the harness and the builder.
1141
1300
  - The packed-install contract test now executes `recipe-quality build` from the published layout, guarding the new dependency's packaging path.
1142
1301
 
1143
1302
  ## 0.7.1
1144
1303
 
1145
1304
  ### Added
1305
+
1146
1306
  - **`doctor --expect-live`** — exit-coded runtime liveness for hooks and scripts: exit 0 only when the adapter runtime decision is `ready` (extension: watcher + CDP; mobile: metro + bridge; core: deps), non-zero with a teaching escape otherwise. Fails closed on every degraded path; reuses the doctor runtime probe, no duplicated checks.
1147
1307
 
1148
1308
  ### Fixed
1309
+
1149
1310
  - **Per-command `--help` documents every implemented flag** (`call/run --list`, `--library`, `--action-manifest`, `logs --window`, `actions --action`, `launch --url`, `doctor --runtime-dir`, `stop --adapter`) and tab-completion matches — the CLI self-teaches its own surface. A contract test guards help/implementation parity.
1150
1311
  - **Injected runner shims resolve at run time** instead of a path baked at inject time: `MM_HARNESS_BIN` → global `mm-harness` on PATH → the recorded install-time path as last resort → teaching escape. A slot copied to or shared with another machine no longer points at a missing absolute path.
1151
1312
  - Removed two parsed-but-unused `launch` flags (`--yes`, `--json-stream`).
@@ -1157,6 +1318,7 @@ The "version people use": every remaining identified fix/improvement batched on
1157
1318
  (0.6.3, below). One themed commit per item; full contract suite green (macOS).
1158
1319
 
1159
1320
  ### Added
1321
+
1160
1322
  - **`call --list` / `run --list`** — one scope-aware listing of everything invocable
1161
1323
  for the detected adapter: the fuzzy short-name action universe (each action's full
1162
1324
  name plus its unambiguous short name — what `call` accepts) and the runnable
@@ -1170,6 +1332,7 @@ The "version people use": every remaining identified fix/improvement batched on
1170
1332
  optional (absent binary → no row, never a doctor failure). Seam: `CAPTURE_HELPER_PATH`.
1171
1333
 
1172
1334
  ### Changed
1335
+
1173
1336
  - **`fixtures set` is never silent** — an upfront intent line the moment work begins,
1174
1337
  plus the mobile setup-wallet leaf's own progress streamed live (heartbeat) during
1175
1338
  the 20-40s wallet build; stdout stays a clean `--json` envelope.
@@ -1190,6 +1353,7 @@ The "version people use": every remaining identified fix/improvement batched on
1190
1353
  `FARMSLOT_ROOT` a back-compat alias for one release.
1191
1354
 
1192
1355
  ### Fixed
1356
+
1193
1357
  - **Extension `launch` teaches `yarn install --immutable` on missing/stale deps** —
1194
1358
  a pre-watcher deps-freshness probe fails fast (teaching only, never auto-installs)
1195
1359
  instead of letting webpack crash deep on stale `node_modules`; gated so a reusable
@@ -1209,6 +1373,7 @@ The "version people use": every remaining identified fix/improvement batched on
1209
1373
  - **Extension quick-tier webpack tail window** is regression-guarded (Metro parity).
1210
1374
 
1211
1375
  ### Internal
1376
+
1212
1377
  - Dependabot: no high/critical advisories remain (existing `resolutions` cover them).
1213
1378
  - Boring-library swaps (pidtree, ajv) declined per the "delete hand-rolled code with a
1214
1379
  bug history" bar: the ps/lsof process detection is contract-tested and working, and
@@ -1225,14 +1390,17 @@ The "version people use": every remaining identified fix/improvement batched on
1225
1390
  ## 0.6.3
1226
1391
 
1227
1392
  ### Fixed
1393
+
1228
1394
  - **Extension `launch` reuses a healthy live runtime instead of always clean-building** — quick `launch` / `launch --sidepanel` on an extension slot now probes for a reusable runtime (webpack watcher LISTENing on `WATCHER_PORT` + CDP `/json/version` reachable on `CDP_PORT` + dist not stale). When all hold it REATTACHES: re-snapshots `dist/chrome` into the loaded runtime-dist and reloads the extension in place over CDP (service worker + pages), re-asserts the side panel, and applies `--url` — against the existing slot Chrome profile, with no watcher stop, webpack cache clear, or Chrome kill/relaunch. Reuse is best-effort: a failed probe (watcher down, CDP unreachable, stale dist) falls through to the clean path, and `--build` is the explicit escape that always clean-builds + relaunches a fresh runtime (no probe) to force-recover a broken slot.
1229
1395
 
1230
1396
  ## 0.6.2 - 2026-07-06
1231
1397
 
1232
1398
  ### Added
1399
+
1233
1400
  - **`-v` / `--version`** prints the mm-harness version.
1234
1401
 
1235
1402
  ### Fixed
1403
+
1236
1404
  - **`logs` now streams live and shows the file path** — it captured output via spawnSync (never shown for a `tail -F` that doesn't exit), so `logs`/`logs --full` looked dead. New spawnInherit hands the terminal to the follow; the resolved log path + `tail -f`/`less +F` hints print upfront so you can page it yourself. Honors the test override seam.
1237
1405
  - **`debug` resolves the checkout's own port** (slot context → pool → formula) like launch/stop/doctor — bare `mm-harness debug` no longer 'fetch failed' against the wrong port.
1238
1406
 
@@ -1241,10 +1409,12 @@ The "version people use": every remaining identified fix/improvement batched on
1241
1409
  Fresh-install fixes found live on a published-0.6.0 install.
1242
1410
 
1243
1411
  ### Fixed
1412
+
1244
1413
  - **Mobile `launch` no longer dies on empty `METAMASK_BUILD_TYPE`** — the quick-launch runs `expo start` directly (bypassing scripts/build.sh, which passes the build type as an arg); the fixture `.js.env` shipped `METAMASK_BUILD_TYPE=""`, which Metro's transform rejects (`Invalid METAMASK_BUILD_TYPE`). start-metro now defaults to the main dev client when unset, so already-installed slots launch without re-syncing fixtures.
1245
1414
  - **`doctor` no longer false-flags orphan Metros** — the detector matched any argv containing "metro" (caught the `tail -F metro.log` viewer) and reported the live, valid bundler as leaked. Now it matches only `expo start` bundlers and excludes the one serving the current port.
1246
1415
 
1247
1416
  ### Internal
1417
+
1248
1418
  - Reap-checkout-metros test hardened against a SIGTERM timing flake (polls for exit).
1249
1419
 
1250
1420
  ## 0.6.0 - 2026-07-06
@@ -1254,6 +1424,7 @@ fresh-slot validation wave — every fix proven live on untouched -2 slots and
1254
1424
  independently cross-reviewed.
1255
1425
 
1256
1426
  ### Changed
1427
+
1257
1428
  - **CLI decomposition + dist-only publish** — `src/cli.ts` is parse+dispatch only;
1258
1429
  handlers under `src/commands/<verb>.ts`; one commander parser; the npm tarball
1259
1430
  ships compiled `dist/` without `src/` and `tsx` is a devDependency (plain-node,
@@ -1263,6 +1434,7 @@ independently cross-reviewed.
1263
1434
  the same slot isolation launch has).
1264
1435
 
1265
1436
  ### Added
1437
+
1266
1438
  - **`stop` reaps leaked Metro bundlers by checkout** (port-agnostic, path-
1267
1439
  boundary matched), `doctor` surfaces any orphan and teaches the reap.
1268
1440
  - **`launch --url <dapp>`** (extension) opens the dapp in the main tab beside the
@@ -1271,6 +1443,7 @@ independently cross-reviewed.
1271
1443
  - DX: run-mode + stale-`dist` warning in `bin`; overlay-delegation notice; docs/CODE-MAP.md.
1272
1444
 
1273
1445
  ### Fixed
1446
+
1274
1447
  - `launch` targets the slot's own simulator (never the `booted` alias) and boots it;
1275
1448
  slot-port resolution layers rungs so a partial rung never nulls the port; provision
1276
1449
  resolves the simulator from the pool and boots before install.
@@ -1284,6 +1457,7 @@ independently cross-reviewed.
1284
1457
  Fresh-install hotfixes found in live validation of 0.5.0.
1285
1458
 
1286
1459
  ### Fixed
1460
+
1287
1461
  - **First launch on a provisioned slot targets the slot's own simulator** — the mobile resolution ladder now reads the `runway-provision.json` baseline (simulator name/udid, watcher port, slot id) when `agentic-runtime.json` doesn't exist yet, and boots that simulator. Previously a provisioned-but-unprepared slot degraded to the simctl `booted` alias, missed the installed dev client, and guessed the wrong Metro port.
1288
1462
  - **`call` works from published installs** — library actions load harness helpers through a dist-preferring bridge instead of importing `src/index.ts` (node refuses to type-strip `.ts` under `node_modules`). The bridge is deliberately narrow (paths + run-recording) to avoid an import cycle through `adapters.ts`.
1289
1463
  - **deps-not-ready guidance matches the caller's state** — on a checkout without node_modules the taught command now leads with `yarn install --immutable` (yarn cannot run scripts at all on a bare checkout), and always ends with the launch re-run.
@@ -1291,19 +1465,26 @@ Fresh-install hotfixes found in live validation of 0.5.0.
1291
1465
  ## Unreleased
1292
1466
 
1293
1467
  ### Fixed
1468
+
1294
1469
  - **`provision --json` adapter-detection failures emit the standard error envelope** — usage errors now match `doctor`/`launch` (envelope on stdout with `error.userAction`; stderr stays human-only). Previously `--json` callers got empty stdout.
1295
1470
 
1296
1471
  ### Changed
1472
+
1297
1473
  - **CLI decomposition** — `src/cli.ts` is parse+dispatch only; handlers live under `src/commands/<verb>.ts`; `provision` routes through `src/commands/provision.ts` (not overlay harness dispatch); launch composition splits under `src/commands/launch/`; commander owns per-command `--help` (no hand-rolled interception).
1298
1474
  - **Published tarball is dist-only** — `files` no longer ships `src/`; `tsx` is a devDependency; `bin/mm-harness` and overlay leaves resolve `dist/*.js` (teaching error if a published install lacks `dist/`). Library actions keep importing `library/actions/harness-exports.mjs` (dist-preferring narrow bridge from 0.5.1); package exports remain for external consumers (`dist/index.js` published; `src/index.ts` when `--conditions=development` and dist is absent).
1299
1475
  - **resolve-farmslot-ports is TypeScript-first** — pool/context/formula port resolution lives in self-contained `adapters/shared/resolve-farmslot-ports-core.mjs` (works from runner and injected overlay copies); `src/adapters/resolve-farmslot-ports.ts` re-exports it for `slot-ports.ts`. `resolve-farmslot-ports.mjs` is the node leaf; `resolve-farmslot-ports.sh` remains a thin bash-compat wrapper for sourced callers.
1300
1476
 
1301
1477
  ## 0.5.0 - 2026-07-05
1478
+
1302
1479
  Minor release: thin Runway provisioning (harness-owned slot provisioning with a shared artifact cache) plus compiled-dist packaging.
1480
+
1303
1481
  ### Added
1482
+
1304
1483
  - **`mm-harness provision runway`** — provisions a mobile slot with exactly: resolve the latest Runway (expo-dev-build) artifact, download it ONCE into a shared machine cache (`~/.cache/metamask-harness/runway/<runId>/`, sha256+size integrity, corrupt entries re-downloaded, never a slot failure), create the simulator if missing, install the dev client on it. Zero yarn/deps/Metro — those stay dispatch-time (lazy, presence-authoritative readiness unchanged). `install --runway` is the overlay-path equivalent (covered by its own contract test). Baseline recorded so doctor reports provisioned-but-deps-pending as a normal state.
1305
1484
  - **Compiled `dist/` for published installs** — the npm tarball ships esbuild-compiled JS; `npm i -g` installs run the dist entry (~0.21s cold vs ~0.37s tsx). Source checkouts without `dist/` keep the tsx dev path; `bin/mm-harness` prefers dist when present. Packaging contract test (pack → global install → dist entry runs).
1485
+
1306
1486
  ### Changed
1487
+
1307
1488
  - Runway provisioning callers (farm installer, mobile-farm `runway` prepare profile) delegate to the harness; the profile no longer runs a deps phase and a contract test enforces that.
1308
1489
 
1309
1490
  ## 0.4.0 - 2026-07-04
@@ -1311,6 +1492,7 @@ Minor release: thin Runway provisioning (harness-owned slot provisioning with a
1311
1492
  Minor release: the adapter-surface architecture plus the day's feature wave (0.3.6-0.3.9 shipped fixes as they were validated live; this cut marks the surface additions honestly).
1312
1493
 
1313
1494
  ### Added
1495
+
1314
1496
  - **AdapterSurface** — one per-platform surface behind a common interface (`src/adapters/surface.ts`: resolveSlotPorts, runtimeStatus, devServer, logSources, hints); doctor/launch/stop/logs/debug/fixtures are surface-backed, so no verb hand-rolls platform branching. Contract: `docs/ADAPTER-SURFACE.md` (never-branch rule, add-a-platform checklist, migration table).
1315
1497
  - **Platform-adaptive `stop` on every adapter** — extension: kills this checkout's webpack watcher (pid file + orphan scan, slot-scoped) and closes its tail window; mobile: port-scoped Metro; core: headless teaching error. Idempotent, outcome-stating summaries.
1316
1498
  - **Doctor reports live runtime state on every adapter** — mobile Metro, extension webpack (via the extension readiness decision), core deps-only — one uniform shape, adapter-scoped colored context rows.
@@ -1318,20 +1500,24 @@ Minor release: the adapter-surface architecture plus the day's feature wave (0.3
1318
1500
  - **Context-aware examples and tier-visible flows** — `call` usage/help examples use the detected adapter's real manifest actions; `flows` prints a precedence legend decoding each source into personal > team > canonical.
1319
1501
 
1320
1502
  ### Changed
1503
+
1321
1504
  - **BREAKING (agent contract)**: `doctor --json` `runtime.metro` (string) is now `runtime.devServer` `{label, status}` and is absent for core.
1322
1505
 
1323
1506
  ### Feature wave recap (0.3.6-0.3.9)
1507
+
1324
1508
  - `mm-harness stop`; live-streamed mobile AND extension launches with upfront intent lines; presence-authoritative readiness (no setup-on-every-launch) with the dev-client running probe; slot-context-first device/port resolution; UTF-8 locale for pod spawns; DEV OVERRIDE banner + SLOT line + colored help; `mmdev-harness` completions; hermetic tmux-stubbed contract suite (now gating CI) + ESLint in `npm run check`; `docs/UX-PRINCIPLES.md` interaction contract.
1325
1509
 
1326
1510
  ## 0.3.9 - 2026-07-04
1327
1511
 
1328
1512
  ### Fixed
1513
+
1329
1514
  - **Readiness is presence-authoritative** — a no-baseline mtime-only stale verdict no longer re-runs `yarn setup` inside every launch (warning instead; fingerprint-verified drift still installs), the deps baseline is recorded after any successful setup, and the `ready` decision probes that the dev client is actually running on the target device before doing nothing (`launch` launches).
1330
1515
  - **Slot-context-first resolution** — device/ports resolve from the checkout's `agentic-runtime.json` before pool/formula fallbacks, for mobile (simulator, Metro port) and extension (CDP + watcher ports, overwriting stale inherited env); naming formulas no longer hardcode pool prefixes.
1331
1516
  - **UTF-8 locale for pod-triggering spawns** — CocoaPods no longer dies on multibyte podspecs under locale-less orchestrator environments.
1332
1517
  - **Hermetic contract tests** — tmux/xcrun/adb are stubbed; the Metro log-window opens only in a run-owned session, never an unrelated attached one.
1333
1518
 
1334
1519
  ### Added
1520
+
1335
1521
  - **`mm-harness stop`** — stop the port-scoped Metro this checkout owns and close its log-tail window; idempotent; teaching error on extension/core.
1336
1522
  - **Extension launch streams live** with an upfront intent line (ports named); mobile and extension long operations are never silent.
1337
1523
  - **Doctor reports live runtime state per platform** (deps currency, Metro on the slot's resolved port, dev-client presence) with adapter-scoped, colored runtime-context rows.
@@ -1339,6 +1525,7 @@ Minor release: the adapter-surface architecture plus the day's feature wave (0.3
1339
1525
  - **docs/UX-PRINCIPLES.md** — the CLI interaction contract (human surface rules; `--json` envelope purity is exempt and inviolable).
1340
1526
 
1341
1527
  ### Fixed
1528
+
1342
1529
  - **Mobile preflight no longer re-runs `yarn setup` on every launch** — the deps readiness decision escalated an mtime hint to `stale` and ran the FULL `yarn setup` (submodules + inpage bridge + jetify + pods) inside the launch phase on every invocation in orchestrated (farmslot) contexts. `depsCheck` reports `stale` for a no-baseline checkout whenever a manifest is newer than the install markers; the orchestrator's git phase refreshes tracked-file mtimes on every sync while its deps phase leaves `node_modules` untouched, so "manifest newer than markers" is normal and not proof of drift — yet it duplicated the orchestrator's deps work and turned a fast preflight into 15+ minutes (the behavior that also looked like a hang before 0.3.8's streaming). **Presence is now authoritative:** without a recorded baseline an mtime-only `stale` is trusted as `current` (with a one-line stderr warning), so a churned-but-present checkout launches with zero `yarn` in both tiers. Genuine drift is still caught by the recorded-baseline fingerprint. `mm-harness launch` (quick tier / no `--build`) also dropped `preflightMode` before deciding, so the fast contract never took effect; it is threaded through now.
1343
1530
  - **Fast tier never installs; genuine gaps teach the orchestrator** — in the fast tier (quick launch / orchestrated), deps that genuinely need work (absent markers, an absent required package, a fingerprint drift, or Metro unable to resolve a module) return a teaching block naming the orchestrator deps/prepare phase (plus the standalone `yarn setup:expo` / `launch --build` remedies) instead of an implicit setup — deps are the orchestrator's contract. The standalone/full tier still installs.
1344
1531
  - **Deps baseline recorded the instant a setup leaf succeeds** — `prepareMobile` records the deps fingerprint baseline immediately after any successful `yarn-setup`, unconditionally (previously only a later `record: true` re-decide did, gated on the original decision being `install` and on `RECIPE_UP_INSTALL_ATTEMPTED`). Genuine-drift detection is fingerprint-based and self-sustaining after one good install.
@@ -1347,6 +1534,7 @@ Minor release: the adapter-surface architecture plus the day's feature wave (0.3
1347
1534
  - **Metro log-tail window no longer leaks into an unrelated tmux session** — running `start-metro` without an explicit session previously fell back to whichever tmux client was current or last attached. The viewer now opens a window only from `RECIPE_TMUX_SESSION` or the checkout runtime context and otherwise skips it; Metro still runs detached to the log. Extracted to `adapters/mobile/lib/tmux-viewer.sh` so the rule is unit-tested; contract tests stub tmux hermetically so no suite run can create a real window.
1348
1535
 
1349
1536
  ### Added
1537
+
1350
1538
  - **Adapter surface — one seam for per-platform behavior** — `src/adapters/surface.ts` defines `AdapterSurface` + a `getAdapterSurface(adapter)` registry that owns the behavior commands used to hand-roll with `if (adapter === …)` ladders (slot-port resolution, read-only runtime status, dev-server describe/stop, log sources, and platform-phrased Next: hints), with thin `mobile`/`extension`/`core` implementations that delegate to the existing readiness/port plumbing (re-homed to `src/adapters/slot-ports.ts`). `doctor`, `launch`, `stop`, `logs`, `debug`, and `fixtures` now resolve platform behavior through the surface instead of branching. This closes the platform-blind class of bug (a platform handled in one command but forgotten in another). Documented in `docs/ADAPTER-SURFACE.md`; enforces `docs/UX-PRINCIPLES.md` principle 1.
1351
1539
  - **`doctor` reports a runtime section for every adapter** — the live runtime readiness line (decision + reasons, deps currency, dev server) now renders for extension (webpack watcher via `decideExtensionReadiness`) and core (deps-presence-only), not just mobile; the render is one uniform shape sourced from the surface.
1352
1540
  - **`mm-harness stop` is adaptive per platform** — mobile stops its port-scoped Metro and closes the log-tail window; extension stops the checkout's webpack watcher (pid file + orphan ps-scan, slot-scoped) and closes its tail window; core teaches the headless path. All idempotent (nothing-to-stop is success), with a uniform summary and `--json` envelope.
@@ -1358,6 +1546,7 @@ Minor release: the adapter-surface architecture plus the day's feature wave (0.3
1358
1546
  - **CLI help surface** — grouped/colored `mm-harness` help, a `DEV OVERRIDE ACTIVE` banner when `MM_HARNESS_BIN` is set, and a `SLOT` line when the checkout is a prepared slot; `mmdev-harness` completion alias.
1359
1547
 
1360
1548
  ### Changed
1549
+
1361
1550
  - **`doctor --json` runtime dev-server shape is normalized** — the mobile-only `runtime.metro` string is replaced by a platform-labelled `runtime.devServer: { label, status }` (`metro` for mobile, `webpack` for extension; absent for headless core), so agents read one shape across platforms.
1362
1551
  - **`yarn check` runs ESLint** — flat-config ESLint (typescript-eslint recommended on `src/**/*.ts`, `@eslint/js` recommended on the `.mjs`/`.cjs` library) runs after typecheck and comment-hygiene. Dev-only (not in the published `files`); the baseline is zero-warning by config scoping.
1363
1552
  - **Deps install uses a scoped `setup:expo` variant when native isn't wanted** — `adapters/mobile/yarn-setup.sh` gains `--mode full|expo`. `expo` runs `yarn setup:expo --no-build-ios --no-build-android` (JS deps + prebuild, no native — native builds come from `open-device.sh`, never doubled); the full `yarn setup` runs only for a native build (`launch --build`). This is also the standalone Metro-only remedy taught by the fast-tier deps block.
@@ -1366,47 +1555,56 @@ Minor release: the adapter-surface architecture plus the day's feature wave (0.3
1366
1555
  ## 0.3.8 - 2026-07-04
1367
1556
 
1368
1557
  ### Fixed
1558
+
1369
1559
  - **Mobile launch streams progress in `--json` mode** — mobile prepare leaves now run through `spawnScriptStreaming`, which tees the child's stdout+stderr to the parent's stderr live while stdout stays reserved for the JSON envelope. A multi-minute `mm-harness launch --json` is no longer silent (previously `spawnSync` buffered leaf output and suppressed it entirely under `--json`). Contract test asserts leaf progress reaches stderr, the envelope stays clean, and the streaming conversion preserves the exit-code contract.
1370
1560
 
1371
1561
  ## 0.3.7 - 2026-07-04
1372
1562
 
1373
1563
  ### Fixed
1564
+
1374
1565
  - **Overlay install manifest no longer advertises the retired `launch.sh`/`live.sh`** — mobile launch/live orchestration is in-process now (`mm-harness launch`), and `install_v1_runner_assets` deletes `scripts/launch.sh` and `scripts/live.sh` on both install paths. The product-owned/metadata-only manifest already advertised `verify` only, but the overlay manifest's `runtimeHelpers` still listed `launch: scripts/launch.sh` and `live: scripts/live.sh` — paths the same install had just removed, a manifest that pointed at nonexistent files. The overlay manifest now advertises `verify` only, matching the product-owned path. A new manifest-honesty contract check in `tests/contract/mobile-inject.test.sh` asserts every advertised `runtimeHelpers` path exists on disk and that `launch`/`live` are not advertised, for both the product-owned and a full overlay install, so this cannot recur in either mode.
1375
1566
 
1376
1567
  ## 0.3.6 - 2026-07-04
1377
1568
 
1378
1569
  ### Fixed
1570
+
1379
1571
  - **Install manifest carries target checkout identity (`targetRevision`)** — `adapters/mobile/inject.sh` stamps the target repo's `HEAD` (resolved once, up front, via `git rev-parse --verify HEAD`) and an explicit `installMode` into the mobile install manifest on both install paths (product-owned/metadata-only and overlay). Consumers (the farm recipe hook) can now gate their fast path on manifest identity matching the current checkout instead of manifest existence, closing the stale-manifest hole where a gitignored manifest survives a branch switch to a bridge-less checkout. Install now fails with teaching guidance when the target `HEAD` cannot be resolved (not-a-repo, unborn branch), instead of stamping an unusable `targetRevision`. Contract tests cover the identity stamp on both paths and the unborn-HEAD refusal.
1380
1572
 
1381
1573
  ## 0.3.5 - 2026-07-04
1382
1574
 
1383
1575
  ### Fixed
1576
+
1384
1577
  - **IMP-24b: shell leaves run even when packed without the exec bit** — `yarn pack` (used by `yarn npm publish`) strips the executable bit off shipped `*.sh` leaves to `644`, even though git stores `100755` and `npm pack` preserves `755`. The 0.3.4 fix (git `100755` + an `npm pack` tarball test) was therefore insufficient: the published `yarn`-packed tarball still shipped `644` leaves and a fresh `npm i -g` hit `EACCES` on the first leaf. `spawnScript` now invokes shell leaves through `bash <leaf>` instead of exec'ing them directly, so the leaf's file mode is irrelevant (node leaves were already interpreter-invoked via `process.execPath`). Env overlays (`FORCE_COLOR`/`RECIPE_RUNTIME_DIR`), arg passing, cwd, JSON envelope, and exit codes are unchanged. New contract test `tests/contract/leaf-644-runs.test.sh` forces the device leaves to mode `644` and drives them through the real `mm-harness` spawn path, asserting they still run — the regression the `npm pack` tarball test could not catch.
1385
1578
 
1386
1579
  ### Changed
1580
+
1387
1581
  - **Publish with `npm publish`, not `yarn npm publish`** — `npm publish` preserves the git `100755` mode on shipped `*.sh` leaves (defense-in-depth on top of the interpreter invocation above). Documented in `AGENTS.md`; git exec bits remain `100755`.
1388
1582
 
1389
1583
  ## 0.3.4 - 2026-07-04
1390
1584
 
1391
1585
  ### Fixed
1586
+
1392
1587
  - **IMP-24: published tarball stripped exec bits from shell leaves** — the harness execs `adapters/**/*.sh` leaves directly, but six shipped leaves (`adapters/mobile/lib/metro-listener.sh`, `adapters/shared/activate-repo-node.sh`, `cli-ux.sh`, `harness-path.sh`, `hash-helpers.sh`, `resolve-farmslot-ports.sh`) were committed without the git exec bit (mode `100644`), so npm packed them `644`. A fresh `npm i -g` then hit `EACCES` on the first leaf. All shipped `*.sh` are now stored `100755` in git, so the published tarball packs them executable. Added contract test `tests/contract/packaging-exec-bits.test.sh` — it runs `npm pack` to produce the actual `.tgz`, then asserts via `tar tzvf` that every shipped `*.sh` entry has owner-execute set; fails with the offending path(s) if any is `644`, so this cannot silently regress.
1393
1588
  - **IMP-24: spawn failures are surfaced, not swallowed** — `spawnScript` returned on `result.error` (a leaf that is missing/`ENOENT` or not executable/`EACCES`) before its human-mode stderr forward, so a leaf that could not start produced a silent exit-1 (surfaced upstream as `MOBILE_PREPARE_FAILED`). The `result.error` branch now always writes a teaching diagnostic (`leaf could not start: <leaf> (<errno>)` + a `Next: reinstall mm-harness` hint) to stderr before returning. Covered by `tests/contract/spawn-error-surfaced.test.sh`.
1394
1589
 
1395
1590
  ## 0.3.3 - 2026-07-04
1396
1591
 
1397
1592
  ### Fixed
1593
+
1398
1594
  - **IMP-23: per-run mobile runtime dir (`RECIPE_RUNTIME_DIR`)** — every on-disk mobile runtime file (`metro.log`, `metro.pid`, `metro.tmux`, `bridge-status.log`, `wallet-fixture.json`) now resolves from the shared runtime-dir resolver that honors `RECIPE_RUNTIME_DIR` instead of the hard-coded `<target>/temp/recipe/runtime`. `RECIPE_RUNTIME_DIR` must be a non-empty relative path under the target checkout — absolute values are rejected at validation. Shell (`recipe_runtime_dir` in `harness-path.sh`, used by `start-metro.sh`, `wait-for-bridge.sh`, `prewarm-bundle.sh`, and `bridge-runtime/setup-wallet.sh`) and TS (`recipeRuntimePath` in `runtime-decision.ts`) resolve the same location, so a run pointed at an isolated subdir (e.g. `temp/recipe/runtime-8081`) writes and reads nothing under the default. Two harness jobs sharing one checkout each set `RECIPE_RUNTIME_DIR` to a distinct relative subdir for full runtime isolation.
1399
1595
  - **IMP-23: port-scoped Metro guard** — the Metro detect/kill/restart helpers are extracted into `adapters/mobile/lib/metro-listener.sh` with an explicit invariant: discovery, inspection, and signalling key off the managed watcher-port only (`lsof -iTCP:<port> -sTCP:LISTEN`). A Metro listening on any other port is never selected, never has its cmdline read, and is never signalled, so a run managing one port cannot detect or kill a concurrent Metro on another port in the same checkout.
1400
1596
 
1401
1597
  ## 0.3.2 - 2026-07-04
1402
1598
 
1403
1599
  ### Fixed
1600
+
1404
1601
  - **IMP-21: FORCE_COLOR-safe pod install** — the mobile pod-triggering leaves (`yarn-setup.sh` running `yarn setup`, and `open-device.sh` running the native `yarn start:ios|android` build) now run with `FORCE_COLOR=0` / `NO_COLOR=1`. VisionCamera's podspec probes `node --print require.resolve('react-native-worklets-core')` and treats any output other than the exact string `undefined` as "found"; an inherited `FORCE_COLOR` made node emit a colorized `undefined`, enabling FrameProcessors and hard-failing on the missing worklets pod. Setup/native builds are now reliable from a `FORCE_COLOR` shell.
1405
1602
  - **IMP-22: `--watcher-port` beats `.js.env WATCHER_PORT`** — `verify.sh` port resolution now honors an explicit `WATCHER_PORT` in the process env (which carries `mm-harness launch --watcher-port N`) over the target's `.js.env`. Precedence is flag > process env > `.js.env` > `8081` default, so a run can be pointed at an alternate Metro/CDP port without editing a shared slot's `.js.env`.
1406
1603
 
1407
1604
  ## 0.3.1 - 2026-07-03
1408
1605
 
1409
1606
  ### Fixed
1607
+
1410
1608
  - **Customization compose-by-ref**: recipes with `{action:"call",ref:"<lib>.<flow>"}` + `--library <lib>=<dir>` now pass pre-run schema validation AND execute at runtime. `validateRecipeAdapterAware` threads `externalFlowIds` into both `validateRecipeDocument` and `validateRecipeWithManifest`; library resolution also runs at `run` time.
1411
1609
  - **IMP-14: stale metro.log guard** — if `metro.log` is older than the install markers (`node_modules/.yarn-state.yml`, `.yarn/install-state.gz`), it is treated as absent so a stale log cannot drive a false native-module-mismatch decision at launch.
1412
1610
  - **IMP-15: stale overlay shim auto-repair (two modes)** — `ensureOverlay` detects (1) a `.runner-source` pointing to a nonexistent runner dir, and (2) a `.runner-source` whose dir exists but whose `bin/mm-harness` was removed. Both re-install automatically before launch.
@@ -1416,9 +1614,11 @@ Minor release: the adapter-surface architecture plus the day's feature wave (0.3
1416
1614
  ## 0.3.0 - 2026-07-03
1417
1615
 
1418
1616
  ### Changed
1617
+
1419
1618
  - Porcelain layer eliminated: the legacy `mm-recipe`/`mme-recipe`/`metamask-recipe` bins are gone; launch/logs/debug/fixtures compose the adapter scripts directly in TypeScript. Repository reorganized into `src/` (all TS), `adapters/` (per-platform runtime glue), `library/` (recipe/action/flow vocabulary).
1420
1619
 
1421
1620
  ### Fixed
1621
+
1422
1622
  - Extension `launch` opens the app directly (composes `live.sh`) instead of a retired command.
1423
1623
  - Mobile `cleanup` resilient to a stale runner-source path.
1424
1624
  - Core run teaches the correct next step when no account is set.