@deeeed/metamask-harness 0.18.0 → 0.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/CHANGELOG.md +187 -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/inject.sh +5 -4
  7. package/adapters/mobile/verify.sh +15 -19
  8. package/adapters/shared/harness-source-fingerprint.mjs +14 -12
  9. package/dist/adapters/harness-freshness.js +49 -0
  10. package/dist/cli.js +0 -4
  11. package/dist/command-contract.js +51 -40
  12. package/dist/command-journal.js +50 -12
  13. package/dist/commands/call.js +95 -20
  14. package/dist/commands/check.js +1 -1
  15. package/dist/commands/completion-candidates.js +7 -19
  16. package/dist/commands/fixtures.js +50 -1
  17. package/dist/commands/last.js +9 -1
  18. package/dist/commands/launch/extension.js +2 -2
  19. package/dist/commands/launch/mobile.js +2 -0
  20. package/dist/commands/list-executables.js +56 -20
  21. package/dist/commands/manifest.js +28 -12
  22. package/dist/commands/parse-args.js +55 -3
  23. package/dist/commands/provision.js +0 -1
  24. package/dist/commands/run-engine.js +514 -336
  25. package/dist/commands/run.js +25 -37
  26. package/dist/commands/shared.js +1 -1
  27. package/dist/heal-bounds.js +5 -0
  28. package/dist/mm-harness-cli.js +22 -30
  29. package/dist/run-diagnostics.js +1 -1
  30. package/dist/run-recording.js +1 -1
  31. package/dist/runner.js +24 -2
  32. package/docs/CONTRIBUTING.md +2 -3
  33. package/docs/QA.md +0 -1
  34. package/docs/RECIPES.md +52 -91
  35. package/library/README.md +5 -5
  36. package/library/actions/core/perps/_controller.mjs +33 -1
  37. package/library/actions/core/perps/assert_orders.mjs +6 -7
  38. package/library/actions/core/perps/assert_positions.mjs +6 -7
  39. package/library/actions/core/perps/close_orders.mjs +2 -0
  40. package/library/actions/core/perps/close_positions.mjs +2 -0
  41. package/library/actions/core/perps/ensure_orders.mjs +4 -2
  42. package/library/actions/core/perps/ensure_positions.mjs +4 -2
  43. package/library/actions/core/perps/place_order.mjs +7 -3
  44. package/library/actions/extension/perps/assert_orders.mjs +2 -1
  45. package/library/actions/extension/perps/assert_positions.mjs +2 -1
  46. package/library/actions/extension/perps/perps.mjs +43 -14
  47. package/library/actions/mobile/perps/assert_orders.mjs +2 -1
  48. package/library/actions/mobile/perps/assert_positions.mjs +2 -1
  49. package/library/actions/mobile/perps/perps.mjs +40 -12
  50. package/library/library.json +1 -1
  51. package/library/manifests/core.action-manifest.json +1170 -413
  52. package/library/manifests/extension.action-manifest.json +1495 -641
  53. package/library/manifests/mobile.action-manifest.json +1704 -769
  54. package/library/recipes/app/lifecycle.android-smoke.mobile.recipe.json +63 -81
  55. package/library/recipes/perps/clean-market-testnet.core.recipe.json +44 -0
  56. package/library/recipes/perps/clean-market-testnet.recipe.json +49 -0
  57. package/library/recipes/perps/lifecycle.recipe.json +136 -180
  58. package/library/recipes/perps/order-lifecycle.core.recipe.json +71 -67
  59. package/library/recipes/perps/performance.background-resume.mobile.recipe.json +51 -67
  60. package/library/recipes/perps/performance.cold-start.mobile.recipe.json +51 -67
  61. package/library/recipes/perps/performance.mobile.recipe.json +37 -51
  62. package/library/recipes/perps/performance.warm-start.mobile.recipe.json +44 -59
  63. package/library/recipes/perps/read-markets.core.recipe.json +29 -31
  64. package/library/recipes/perps/smoke.core.recipe.json +29 -32
  65. package/library/recipes/perps/smoke.extension.recipe.json +41 -44
  66. package/library/recipes/perps/smoke.mobile.recipe.json +42 -44
  67. package/library/recipes/perps/trading-lifecycle.core.recipe.json +69 -65
  68. package/library/recipes/runner/action-validation.extension.recipe.json +312 -405
  69. package/library/recipes/runner/action-validation.mobile.recipe.json +316 -409
  70. package/library/recipes/runner/smoke.core.recipe.json +18 -20
  71. package/library/recipes/runner/smoke.extension.recipe.json +23 -24
  72. package/library/recipes/runner/smoke.mobile.recipe.json +23 -24
  73. package/library/recipes/wallet/smoke.extension.recipe.json +33 -35
  74. package/library/recipes/wallet/smoke.mobile.recipe.json +33 -35
  75. package/package.json +3 -3
  76. package/scripts/completions.sh +1 -4
  77. package/dist/adapters/extension/harness-freshness.js +0 -39
  78. package/dist/commands/flows.js +0 -91
  79. package/library/flows/perps.flows.json +0 -64
package/CHANGELOG.md CHANGED
@@ -2,6 +2,45 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.19.0 - 2026-07-22
6
+
7
+ ### Added
8
+
9
+ - Discover a task recipe's adjacent `recipe-library/` without extra flags.
10
+ - Recipe execution now accepts typed root `key=value` parameters, applies defaults, and resolves nested recipes through the same adapter-aware index.
11
+ - `run --list` and `run --describe` now expose parameter defaults, adapter variants, nested recipes, sources, and shadows.
12
+ - Runs now retain exact digest-keyed dependency recipes and `recipe-resolution.json`.
13
+
14
+ ### Changed
15
+
16
+ - Made bare `mm-harness fixtures` report the current fixture status and next action instead of failing with an option dump.
17
+ - Simplified CLI usage failures and `run --help`; removed duplicate `--project-root`, `--record`, and logical-adapter `--platform` spellings while retaining `--platform ios|android`.
18
+ - Direct calls teach missing/invalid values, show declared defaults, omit routine library provenance, and reject implicit Perps assertion/mutation targets.
19
+ - Perps reads without a selector consistently return all live items across Extension, Mobile, and Core.
20
+ - `run` errors lead to recipe discovery instead of dumping every flag; help separates common overrides from managed integration controls.
21
+ - Declared team and task-library actions retain source provenance through the packaged adapter path.
22
+ - Validate every bundled recipe dependency graph during `yarn check` so missing or cyclic `call.ref` values fail before runtime.
23
+ - Require Farmslot protocol `^0.11.0` and recipe-harness `^0.9.0` for composable Recipe v1 execution.
24
+ - Discovery now exposes callable actions through `call --list` and executable recipes through `run --list`.
25
+
26
+ ### Fixed
27
+
28
+ - Deduplicate recipe-library output and reduce application failures to their actionable message while retaining full trace detail.
29
+ - `last` now shows the redacted command and evidence paths, including automatically resolved run/call artifact directories.
30
+ - `run --describe` reports the recipe's exact `$schema` URL instead of a fabricated nested version.
31
+ - `run --plan` rejects invalid parameters passed into nested recipes.
32
+ - Command journals redact sensitive keys inside standalone JSON arguments.
33
+ - Core Perps actions support the current Core `tsx` module export shape.
34
+ - Recipe delegation preserves upstream non-zero exit status and uses the public `mm-harness` name in recovery commands.
35
+ - Recipe discovery and repeated `--library` flags preserve every source in declared precedence order.
36
+
37
+ ### Removed
38
+
39
+ - Removed redundant `--project-root`, bare `--record`, and logical-adapter uses of `run`/`call --platform`; use `--target`, `--record-video`, and `--adapter`.
40
+ - Removed flow catalogs, flow promotion, the `flows` command, and their completion surface; reusable graphs are composable Recipe v1 documents.
41
+
42
+ ## 0.18.0 - 2026-07-20
43
+
5
44
  ### Added
6
45
 
7
46
  - Recipe runs now preflight source provenance and action risk before runtime preparation; untrusted restricted plans require exact-digest approval.
@@ -98,11 +137,13 @@
98
137
  - `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
138
 
100
139
  ### Added
140
+
101
141
  - 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
142
  - `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
143
  - `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
144
 
105
145
  ### Fixed
146
+
106
147
  - 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
148
  - `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
149
  - `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 +176,27 @@
135
176
  ## 0.14.7 - 2026-07-10
136
177
 
137
178
  ### Changed
179
+
138
180
  - `doctor --print-ready` now implies the exit-coded live probe — Farmslot `health_check` hooks need only `--print-ready`, not `--expect-live --print-ready`.
139
181
  - Mobile `--print-ready` uses bridge `walletState` (unlocked + live bridge), not React Navigation route names; mobile `ready_indicator` is `OK`.
140
182
  - Removed `metamask-recipe` install symlinks from adapter inject paths (no compat aliases).
141
183
  - `doctor --print-ready` rejects `--json` (stdout is reserved for the Farmslot indicator line).
142
184
 
143
185
  ### Fixed
186
+
144
187
  - Mobile `surface.runtimeStatus` infers Android from `ADB_SERIAL` / `ANDROID_SERIAL` when present.
145
188
 
146
189
  ## 0.14.6 - 2026-07-10
147
190
 
148
191
  ### Added
192
+
149
193
  - `mm-harness doctor --print-ready` — Farmslot `health_check` mode: prints `health.ready_indicator` on stdout (`extension`/`mobile`: `OK`; `core`: `ready`).
150
194
  - `doctor --expect-live` — exit-coded liveness gate without indicator output (prepare recovery and other pass/fail callers).
151
195
 
152
196
  ## 0.14.5 - 2026-07-10
153
197
 
154
198
  ### Fixed
199
+
155
200
  - Extension `ensure-browser` closes disposable New Tab / blank / extensions pages after reopen, matching reattach/ensure-ready hygiene.
156
201
  - 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
202
  - 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 +205,13 @@
160
205
  ## 0.14.4 - 2026-07-09
161
206
 
162
207
  ### Fixed
208
+
163
209
  - Include the new extension viewer cleanup scripts in the adapter manifest so published packages pass adapter-surface validation.
164
210
 
165
211
  ## 0.14.3 - 2026-07-09
166
212
 
167
213
  ### Fixed
214
+
168
215
  - 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
216
  - Extension sidepanel launch waits longer for Chrome to expose the `sidepanel.html` CDP target, avoiding false failures after Chrome accepts the open request.
170
217
  - 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 +220,18 @@
173
220
  ## 0.14.2 - 2026-07-09
174
221
 
175
222
  ### Fixed
223
+
176
224
  - 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
225
 
178
226
  ## 0.14.1 - 2026-07-09
179
227
 
180
228
  ### Added
229
+
181
230
  - `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
231
  - `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
232
 
184
233
  ### Fixed
234
+
185
235
  - 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
236
  - 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
237
  - 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 +248,13 @@
198
248
  ## 0.14.0 - 2026-07-08
199
249
 
200
250
  ### Added
251
+
201
252
  - `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
253
  - 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
254
  - 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
255
 
205
256
  ### Fixed
257
+
206
258
  - 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
259
  - 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
260
  - 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 +267,13 @@
215
267
  ## 0.13.0 - 2026-07-08
216
268
 
217
269
  ### Added
270
+
218
271
  - 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
272
  - 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
273
  - `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
274
 
222
275
  ### Fixed
276
+
223
277
  - 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
278
  - 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
279
  - 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 +282,11 @@
228
282
  ## 0.12.0 - 2026-07-08
229
283
 
230
284
  ### Added
285
+
231
286
  - 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
287
 
233
288
  ### Fixed
289
+
234
290
  - `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
291
  - 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
292
  - `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 +295,17 @@
239
295
  ## 0.11.0 - 2026-07-07
240
296
 
241
297
  ### Added
298
+
242
299
  - 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
300
  - `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
301
  - 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
302
 
246
303
  ### Changed
304
+
247
305
  - `@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
306
 
249
307
  ### Fixed
308
+
250
309
  - `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
310
  - 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
311
  - `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 +317,20 @@
258
317
  ## 0.10.0 - 2026-07-07
259
318
 
260
319
  ### Added
320
+
261
321
  - **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
322
  - **`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
323
  - **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
324
  - **`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
325
 
266
326
  ### Fixed
327
+
267
328
  - **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
329
 
269
330
  ## 0.9.1 - 2026-07-07
270
331
 
271
332
  ### Fixed
333
+
272
334
  - **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
335
  - **`--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
336
  - **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 +339,14 @@
277
339
  ## 0.9.0 - 2026-07-07
278
340
 
279
341
  ### Added
342
+
280
343
  - **`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
344
  - **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
345
 
283
346
  ## 0.8.0 - 2026-07-07
284
347
 
285
348
  ### Added
349
+
286
350
  - **`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
351
  - **`--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
352
  - **`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 +354,58 @@
290
354
  ## 0.7.5 - 2026-07-07
291
355
 
292
356
  ### Fixed
357
+
293
358
  - **`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
359
  - **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
360
 
296
361
  ### Changed
362
+
297
363
  - **`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
364
 
299
365
  ## 0.7.4 - 2026-07-07
300
366
 
301
367
  ### Added
368
+
302
369
  - **`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
370
  - **`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
371
 
305
372
  ### Fixed
373
+
306
374
  - **`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
375
  - **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
376
  - **`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
377
 
310
378
  ### Changed
379
+
311
380
  - **`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
381
  - **`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
382
 
314
383
  ## 0.7.3
315
384
 
316
385
  ### Fixed
386
+
317
387
  - **`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
388
  - **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
389
 
320
390
  ## 0.7.2
321
391
 
322
392
  ### Added
393
+
323
394
  - **`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
395
 
325
396
  ### Changed
397
+
326
398
  - `@farmslot/protocol` dependency raised to `^0.7.3` so one protocol version serves both the harness and the builder.
327
399
  - The packed-install contract test now executes `recipe-quality build` from the published layout, guarding the new dependency's packaging path.
328
400
 
329
401
  ## 0.7.1
330
402
 
331
403
  ### Added
404
+
332
405
  - **`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
406
 
334
407
  ### Fixed
408
+
335
409
  - **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
410
  - **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
411
  - Removed two parsed-but-unused `launch` flags (`--yes`, `--json-stream`).
@@ -343,6 +417,7 @@ The "version people use": every remaining identified fix/improvement batched on
343
417
  (0.6.3, below). One themed commit per item; full contract suite green (macOS).
344
418
 
345
419
  ### Added
420
+
346
421
  - **`call --list` / `run --list`** — one scope-aware listing of everything invocable
347
422
  for the detected adapter: the fuzzy short-name action universe (each action's full
348
423
  name plus its unambiguous short name — what `call` accepts) and the runnable
@@ -356,6 +431,7 @@ The "version people use": every remaining identified fix/improvement batched on
356
431
  optional (absent binary → no row, never a doctor failure). Seam: `CAPTURE_HELPER_PATH`.
357
432
 
358
433
  ### Changed
434
+
359
435
  - **`fixtures set` is never silent** — an upfront intent line the moment work begins,
360
436
  plus the mobile setup-wallet leaf's own progress streamed live (heartbeat) during
361
437
  the 20-40s wallet build; stdout stays a clean `--json` envelope.
@@ -376,6 +452,7 @@ The "version people use": every remaining identified fix/improvement batched on
376
452
  `FARMSLOT_ROOT` a back-compat alias for one release.
377
453
 
378
454
  ### Fixed
455
+
379
456
  - **Extension `launch` teaches `yarn install --immutable` on missing/stale deps** —
380
457
  a pre-watcher deps-freshness probe fails fast (teaching only, never auto-installs)
381
458
  instead of letting webpack crash deep on stale `node_modules`; gated so a reusable
@@ -395,6 +472,7 @@ The "version people use": every remaining identified fix/improvement batched on
395
472
  - **Extension quick-tier webpack tail window** is regression-guarded (Metro parity).
396
473
 
397
474
  ### Internal
475
+
398
476
  - Dependabot: no high/critical advisories remain (existing `resolutions` cover them).
399
477
  - Boring-library swaps (pidtree, ajv) declined per the "delete hand-rolled code with a
400
478
  bug history" bar: the ps/lsof process detection is contract-tested and working, and
@@ -411,14 +489,17 @@ The "version people use": every remaining identified fix/improvement batched on
411
489
  ## 0.6.3
412
490
 
413
491
  ### Fixed
492
+
414
493
  - **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
494
 
416
495
  ## 0.6.2 - 2026-07-06
417
496
 
418
497
  ### Added
498
+
419
499
  - **`-v` / `--version`** prints the mm-harness version.
420
500
 
421
501
  ### Fixed
502
+
422
503
  - **`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
504
  - **`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
505
 
@@ -427,10 +508,12 @@ The "version people use": every remaining identified fix/improvement batched on
427
508
  Fresh-install fixes found live on a published-0.6.0 install.
428
509
 
429
510
  ### Fixed
511
+
430
512
  - **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
513
  - **`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
514
 
433
515
  ### Internal
516
+
434
517
  - Reap-checkout-metros test hardened against a SIGTERM timing flake (polls for exit).
435
518
 
436
519
  ## 0.6.0 - 2026-07-06
@@ -440,6 +523,7 @@ fresh-slot validation wave — every fix proven live on untouched -2 slots and
440
523
  independently cross-reviewed.
441
524
 
442
525
  ### Changed
526
+
443
527
  - **CLI decomposition + dist-only publish** — `src/cli.ts` is parse+dispatch only;
444
528
  handlers under `src/commands/<verb>.ts`; one commander parser; the npm tarball
445
529
  ships compiled `dist/` without `src/` and `tsx` is a devDependency (plain-node,
@@ -449,6 +533,7 @@ independently cross-reviewed.
449
533
  the same slot isolation launch has).
450
534
 
451
535
  ### Added
536
+
452
537
  - **`stop` reaps leaked Metro bundlers by checkout** (port-agnostic, path-
453
538
  boundary matched), `doctor` surfaces any orphan and teaches the reap.
454
539
  - **`launch --url <dapp>`** (extension) opens the dapp in the main tab beside the
@@ -457,6 +542,7 @@ independently cross-reviewed.
457
542
  - DX: run-mode + stale-`dist` warning in `bin`; overlay-delegation notice; docs/CODE-MAP.md.
458
543
 
459
544
  ### Fixed
545
+
460
546
  - `launch` targets the slot's own simulator (never the `booted` alias) and boots it;
461
547
  slot-port resolution layers rungs so a partial rung never nulls the port; provision
462
548
  resolves the simulator from the pool and boots before install.
@@ -470,6 +556,7 @@ independently cross-reviewed.
470
556
  Fresh-install hotfixes found in live validation of 0.5.0.
471
557
 
472
558
  ### Fixed
559
+
473
560
  - **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
561
  - **`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
562
  - **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 +564,26 @@ Fresh-install hotfixes found in live validation of 0.5.0.
477
564
  ## Unreleased
478
565
 
479
566
  ### Fixed
567
+
480
568
  - **`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
569
 
482
570
  ### Changed
571
+
483
572
  - **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
573
  - **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
574
  - **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
575
 
487
576
  ## 0.5.0 - 2026-07-05
577
+
488
578
  Minor release: thin Runway provisioning (harness-owned slot provisioning with a shared artifact cache) plus compiled-dist packaging.
579
+
489
580
  ### Added
581
+
490
582
  - **`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
583
  - **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).
584
+
492
585
  ### Changed
586
+
493
587
  - 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
588
 
495
589
  ## 0.4.0 - 2026-07-04
@@ -497,6 +591,7 @@ Minor release: thin Runway provisioning (harness-owned slot provisioning with a
497
591
  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
592
 
499
593
  ### Added
594
+
500
595
  - **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
596
  - **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
597
  - **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 +599,24 @@ Minor release: the adapter-surface architecture plus the day's feature wave (0.3
504
599
  - **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
600
 
506
601
  ### Changed
602
+
507
603
  - **BREAKING (agent contract)**: `doctor --json` `runtime.metro` (string) is now `runtime.devServer` `{label, status}` and is absent for core.
508
604
 
509
605
  ### Feature wave recap (0.3.6-0.3.9)
606
+
510
607
  - `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
608
 
512
609
  ## 0.3.9 - 2026-07-04
513
610
 
514
611
  ### Fixed
612
+
515
613
  - **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
614
  - **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
615
  - **UTF-8 locale for pod-triggering spawns** — CocoaPods no longer dies on multibyte podspecs under locale-less orchestrator environments.
518
616
  - **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
617
 
520
618
  ### Added
619
+
521
620
  - **`mm-harness stop`** — stop the port-scoped Metro this checkout owns and close its log-tail window; idempotent; teaching error on extension/core.
522
621
  - **Extension launch streams live** with an upfront intent line (ports named); mobile and extension long operations are never silent.
523
622
  - **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 +624,7 @@ Minor release: the adapter-surface architecture plus the day's feature wave (0.3
525
624
  - **docs/UX-PRINCIPLES.md** — the CLI interaction contract (human surface rules; `--json` envelope purity is exempt and inviolable).
526
625
 
527
626
  ### Fixed
627
+
528
628
  - **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
629
  - **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
630
  - **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 +633,7 @@ Minor release: the adapter-surface architecture plus the day's feature wave (0.3
533
633
  - **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
634
 
535
635
  ### Added
636
+
536
637
  - **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
638
  - **`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
639
  - **`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 +645,7 @@ Minor release: the adapter-surface architecture plus the day's feature wave (0.3
544
645
  - **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
646
 
546
647
  ### Changed
648
+
547
649
  - **`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
650
  - **`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
651
  - **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 +654,56 @@ Minor release: the adapter-surface architecture plus the day's feature wave (0.3
552
654
  ## 0.3.8 - 2026-07-04
553
655
 
554
656
  ### Fixed
657
+
555
658
  - **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
659
 
557
660
  ## 0.3.7 - 2026-07-04
558
661
 
559
662
  ### Fixed
663
+
560
664
  - **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
665
 
562
666
  ## 0.3.6 - 2026-07-04
563
667
 
564
668
  ### Fixed
669
+
565
670
  - **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
671
 
567
672
  ## 0.3.5 - 2026-07-04
568
673
 
569
674
  ### Fixed
675
+
570
676
  - **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
677
 
572
678
  ### Changed
679
+
573
680
  - **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
681
 
575
682
  ## 0.3.4 - 2026-07-04
576
683
 
577
684
  ### Fixed
685
+
578
686
  - **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
687
  - **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
688
 
581
689
  ## 0.3.3 - 2026-07-04
582
690
 
583
691
  ### Fixed
692
+
584
693
  - **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
694
  - **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
695
 
587
696
  ## 0.3.2 - 2026-07-04
588
697
 
589
698
  ### Fixed
699
+
590
700
  - **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
701
  - **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
702
 
593
703
  ## 0.3.1 - 2026-07-03
594
704
 
595
705
  ### Fixed
706
+
596
707
  - **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
708
  - **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
709
  - **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 +713,11 @@ Minor release: the adapter-surface architecture plus the day's feature wave (0.3
602
713
  ## 0.3.0 - 2026-07-03
603
714
 
604
715
  ### Changed
716
+
605
717
  - 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
718
 
607
719
  ### Fixed
720
+
608
721
  - Extension `launch` opens the app directly (composes `live.sh`) instead of a retired command.
609
722
  - Mobile `cleanup` resilient to a stale runner-source path.
610
723
  - Core run teaches the correct next step when no account is set.
@@ -907,11 +1020,13 @@ Minor release: the adapter-surface architecture plus the day's feature wave (0.3
907
1020
  - `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
1021
 
909
1022
  ### Added
1023
+
910
1024
  - 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
1025
  - `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
1026
  - `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
1027
 
914
1028
  ### Fixed
1029
+
915
1030
  - 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
1031
  - 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
1032
  - 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 +1064,27 @@ Minor release: the adapter-surface architecture plus the day's feature wave (0.3
949
1064
  ## 0.14.7 - 2026-07-10
950
1065
 
951
1066
  ### Changed
1067
+
952
1068
  - `doctor --print-ready` now implies the exit-coded live probe — Farmslot `health_check` hooks need only `--print-ready`, not `--expect-live --print-ready`.
953
1069
  - Mobile `--print-ready` uses bridge `walletState` (unlocked + live bridge), not React Navigation route names; mobile `ready_indicator` is `OK`.
954
1070
  - Removed `metamask-recipe` install symlinks from adapter inject paths (no compat aliases).
955
1071
  - `doctor --print-ready` rejects `--json` (stdout is reserved for the Farmslot indicator line).
956
1072
 
957
1073
  ### Fixed
1074
+
958
1075
  - Mobile `surface.runtimeStatus` infers Android from `ADB_SERIAL` / `ANDROID_SERIAL` when present.
959
1076
 
960
1077
  ## 0.14.6 - 2026-07-10
961
1078
 
962
1079
  ### Added
1080
+
963
1081
  - `mm-harness doctor --print-ready` — Farmslot `health_check` mode: prints `health.ready_indicator` on stdout (`extension`/`mobile`: `OK`; `core`: `ready`).
964
1082
  - `doctor --expect-live` — exit-coded liveness gate without indicator output (prepare recovery and other pass/fail callers).
965
1083
 
966
1084
  ## 0.14.5 - 2026-07-10
967
1085
 
968
1086
  ### Fixed
1087
+
969
1088
  - Extension `ensure-browser` closes disposable New Tab / blank / extensions pages after reopen, matching reattach/ensure-ready hygiene.
970
1089
  - 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
1090
  - 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 +1093,13 @@ Minor release: the adapter-surface architecture plus the day's feature wave (0.3
974
1093
  ## 0.14.4 - 2026-07-09
975
1094
 
976
1095
  ### Fixed
1096
+
977
1097
  - Include the new extension viewer cleanup scripts in the adapter manifest so published packages pass adapter-surface validation.
978
1098
 
979
1099
  ## 0.14.3 - 2026-07-09
980
1100
 
981
1101
  ### Fixed
1102
+
982
1103
  - 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
1104
  - Extension sidepanel launch waits longer for Chrome to expose the `sidepanel.html` CDP target, avoiding false failures after Chrome accepts the open request.
984
1105
  - 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 +1108,18 @@ Minor release: the adapter-surface architecture plus the day's feature wave (0.3
987
1108
  ## 0.14.2 - 2026-07-09
988
1109
 
989
1110
  ### Fixed
1111
+
990
1112
  - 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
1113
 
992
1114
  ## 0.14.1 - 2026-07-09
993
1115
 
994
1116
  ### Added
1117
+
995
1118
  - `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
1119
  - `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
1120
 
998
1121
  ### Fixed
1122
+
999
1123
  - 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
1124
  - 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
1125
  - 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 +1136,13 @@ Minor release: the adapter-surface architecture plus the day's feature wave (0.3
1012
1136
  ## 0.14.0 - 2026-07-08
1013
1137
 
1014
1138
  ### Added
1139
+
1015
1140
  - `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
1141
  - 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
1142
  - 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
1143
 
1019
1144
  ### Fixed
1145
+
1020
1146
  - 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
1147
  - 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
1148
  - 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 +1155,13 @@ Minor release: the adapter-surface architecture plus the day's feature wave (0.3
1029
1155
  ## 0.13.0 - 2026-07-08
1030
1156
 
1031
1157
  ### Added
1158
+
1032
1159
  - 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
1160
  - 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
1161
  - `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
1162
 
1036
1163
  ### Fixed
1164
+
1037
1165
  - 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
1166
  - 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
1167
  - 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 +1170,11 @@ Minor release: the adapter-surface architecture plus the day's feature wave (0.3
1042
1170
  ## 0.12.0 - 2026-07-08
1043
1171
 
1044
1172
  ### Added
1173
+
1045
1174
  - 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
1175
 
1047
1176
  ### Fixed
1177
+
1048
1178
  - `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
1179
  - 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
1180
  - `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 +1183,17 @@ Minor release: the adapter-surface architecture plus the day's feature wave (0.3
1053
1183
  ## 0.11.0 - 2026-07-07
1054
1184
 
1055
1185
  ### Added
1186
+
1056
1187
  - 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
1188
  - `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
1189
  - 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
1190
 
1060
1191
  ### Changed
1192
+
1061
1193
  - `@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
1194
 
1063
1195
  ### Fixed
1196
+
1064
1197
  - `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
1198
  - 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
1199
  - `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 +1205,20 @@ Minor release: the adapter-surface architecture plus the day's feature wave (0.3
1072
1205
  ## 0.10.0 - 2026-07-07
1073
1206
 
1074
1207
  ### Added
1208
+
1075
1209
  - **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
1210
  - **`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
1211
  - **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
1212
  - **`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
1213
 
1080
1214
  ### Fixed
1215
+
1081
1216
  - **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
1217
 
1083
1218
  ## 0.9.1 - 2026-07-07
1084
1219
 
1085
1220
  ### Fixed
1221
+
1086
1222
  - **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
1223
  - **`--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
1224
  - **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 +1227,14 @@ Minor release: the adapter-surface architecture plus the day's feature wave (0.3
1091
1227
  ## 0.9.0 - 2026-07-07
1092
1228
 
1093
1229
  ### Added
1230
+
1094
1231
  - **`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
1232
  - **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
1233
 
1097
1234
  ## 0.8.0 - 2026-07-07
1098
1235
 
1099
1236
  ### Added
1237
+
1100
1238
  - **`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
1239
  - **`--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
1240
  - **`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 +1242,58 @@ Minor release: the adapter-surface architecture plus the day's feature wave (0.3
1104
1242
  ## 0.7.5 - 2026-07-07
1105
1243
 
1106
1244
  ### Fixed
1245
+
1107
1246
  - **`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
1247
  - **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
1248
 
1110
1249
  ### Changed
1250
+
1111
1251
  - **`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
1252
 
1113
1253
  ## 0.7.4 - 2026-07-07
1114
1254
 
1115
1255
  ### Added
1256
+
1116
1257
  - **`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
1258
  - **`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
1259
 
1119
1260
  ### Fixed
1261
+
1120
1262
  - **`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
1263
  - **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
1264
  - **`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
1265
 
1124
1266
  ### Changed
1267
+
1125
1268
  - **`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
1269
  - **`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
1270
 
1128
1271
  ## 0.7.3
1129
1272
 
1130
1273
  ### Fixed
1274
+
1131
1275
  - **`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
1276
  - **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
1277
 
1134
1278
  ## 0.7.2
1135
1279
 
1136
1280
  ### Added
1281
+
1137
1282
  - **`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
1283
 
1139
1284
  ### Changed
1285
+
1140
1286
  - `@farmslot/protocol` dependency raised to `^0.7.3` so one protocol version serves both the harness and the builder.
1141
1287
  - The packed-install contract test now executes `recipe-quality build` from the published layout, guarding the new dependency's packaging path.
1142
1288
 
1143
1289
  ## 0.7.1
1144
1290
 
1145
1291
  ### Added
1292
+
1146
1293
  - **`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
1294
 
1148
1295
  ### Fixed
1296
+
1149
1297
  - **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
1298
  - **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
1299
  - Removed two parsed-but-unused `launch` flags (`--yes`, `--json-stream`).
@@ -1157,6 +1305,7 @@ The "version people use": every remaining identified fix/improvement batched on
1157
1305
  (0.6.3, below). One themed commit per item; full contract suite green (macOS).
1158
1306
 
1159
1307
  ### Added
1308
+
1160
1309
  - **`call --list` / `run --list`** — one scope-aware listing of everything invocable
1161
1310
  for the detected adapter: the fuzzy short-name action universe (each action's full
1162
1311
  name plus its unambiguous short name — what `call` accepts) and the runnable
@@ -1170,6 +1319,7 @@ The "version people use": every remaining identified fix/improvement batched on
1170
1319
  optional (absent binary → no row, never a doctor failure). Seam: `CAPTURE_HELPER_PATH`.
1171
1320
 
1172
1321
  ### Changed
1322
+
1173
1323
  - **`fixtures set` is never silent** — an upfront intent line the moment work begins,
1174
1324
  plus the mobile setup-wallet leaf's own progress streamed live (heartbeat) during
1175
1325
  the 20-40s wallet build; stdout stays a clean `--json` envelope.
@@ -1190,6 +1340,7 @@ The "version people use": every remaining identified fix/improvement batched on
1190
1340
  `FARMSLOT_ROOT` a back-compat alias for one release.
1191
1341
 
1192
1342
  ### Fixed
1343
+
1193
1344
  - **Extension `launch` teaches `yarn install --immutable` on missing/stale deps** —
1194
1345
  a pre-watcher deps-freshness probe fails fast (teaching only, never auto-installs)
1195
1346
  instead of letting webpack crash deep on stale `node_modules`; gated so a reusable
@@ -1209,6 +1360,7 @@ The "version people use": every remaining identified fix/improvement batched on
1209
1360
  - **Extension quick-tier webpack tail window** is regression-guarded (Metro parity).
1210
1361
 
1211
1362
  ### Internal
1363
+
1212
1364
  - Dependabot: no high/critical advisories remain (existing `resolutions` cover them).
1213
1365
  - Boring-library swaps (pidtree, ajv) declined per the "delete hand-rolled code with a
1214
1366
  bug history" bar: the ps/lsof process detection is contract-tested and working, and
@@ -1225,14 +1377,17 @@ The "version people use": every remaining identified fix/improvement batched on
1225
1377
  ## 0.6.3
1226
1378
 
1227
1379
  ### Fixed
1380
+
1228
1381
  - **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
1382
 
1230
1383
  ## 0.6.2 - 2026-07-06
1231
1384
 
1232
1385
  ### Added
1386
+
1233
1387
  - **`-v` / `--version`** prints the mm-harness version.
1234
1388
 
1235
1389
  ### Fixed
1390
+
1236
1391
  - **`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
1392
  - **`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
1393
 
@@ -1241,10 +1396,12 @@ The "version people use": every remaining identified fix/improvement batched on
1241
1396
  Fresh-install fixes found live on a published-0.6.0 install.
1242
1397
 
1243
1398
  ### Fixed
1399
+
1244
1400
  - **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
1401
  - **`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
1402
 
1247
1403
  ### Internal
1404
+
1248
1405
  - Reap-checkout-metros test hardened against a SIGTERM timing flake (polls for exit).
1249
1406
 
1250
1407
  ## 0.6.0 - 2026-07-06
@@ -1254,6 +1411,7 @@ fresh-slot validation wave — every fix proven live on untouched -2 slots and
1254
1411
  independently cross-reviewed.
1255
1412
 
1256
1413
  ### Changed
1414
+
1257
1415
  - **CLI decomposition + dist-only publish** — `src/cli.ts` is parse+dispatch only;
1258
1416
  handlers under `src/commands/<verb>.ts`; one commander parser; the npm tarball
1259
1417
  ships compiled `dist/` without `src/` and `tsx` is a devDependency (plain-node,
@@ -1263,6 +1421,7 @@ independently cross-reviewed.
1263
1421
  the same slot isolation launch has).
1264
1422
 
1265
1423
  ### Added
1424
+
1266
1425
  - **`stop` reaps leaked Metro bundlers by checkout** (port-agnostic, path-
1267
1426
  boundary matched), `doctor` surfaces any orphan and teaches the reap.
1268
1427
  - **`launch --url <dapp>`** (extension) opens the dapp in the main tab beside the
@@ -1271,6 +1430,7 @@ independently cross-reviewed.
1271
1430
  - DX: run-mode + stale-`dist` warning in `bin`; overlay-delegation notice; docs/CODE-MAP.md.
1272
1431
 
1273
1432
  ### Fixed
1433
+
1274
1434
  - `launch` targets the slot's own simulator (never the `booted` alias) and boots it;
1275
1435
  slot-port resolution layers rungs so a partial rung never nulls the port; provision
1276
1436
  resolves the simulator from the pool and boots before install.
@@ -1284,6 +1444,7 @@ independently cross-reviewed.
1284
1444
  Fresh-install hotfixes found in live validation of 0.5.0.
1285
1445
 
1286
1446
  ### Fixed
1447
+
1287
1448
  - **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
1449
  - **`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
1450
  - **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 +1452,26 @@ Fresh-install hotfixes found in live validation of 0.5.0.
1291
1452
  ## Unreleased
1292
1453
 
1293
1454
  ### Fixed
1455
+
1294
1456
  - **`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
1457
 
1296
1458
  ### Changed
1459
+
1297
1460
  - **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
1461
  - **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
1462
  - **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
1463
 
1301
1464
  ## 0.5.0 - 2026-07-05
1465
+
1302
1466
  Minor release: thin Runway provisioning (harness-owned slot provisioning with a shared artifact cache) plus compiled-dist packaging.
1467
+
1303
1468
  ### Added
1469
+
1304
1470
  - **`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
1471
  - **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).
1472
+
1306
1473
  ### Changed
1474
+
1307
1475
  - 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
1476
 
1309
1477
  ## 0.4.0 - 2026-07-04
@@ -1311,6 +1479,7 @@ Minor release: thin Runway provisioning (harness-owned slot provisioning with a
1311
1479
  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
1480
 
1313
1481
  ### Added
1482
+
1314
1483
  - **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
1484
  - **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
1485
  - **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 +1487,24 @@ Minor release: the adapter-surface architecture plus the day's feature wave (0.3
1318
1487
  - **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
1488
 
1320
1489
  ### Changed
1490
+
1321
1491
  - **BREAKING (agent contract)**: `doctor --json` `runtime.metro` (string) is now `runtime.devServer` `{label, status}` and is absent for core.
1322
1492
 
1323
1493
  ### Feature wave recap (0.3.6-0.3.9)
1494
+
1324
1495
  - `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
1496
 
1326
1497
  ## 0.3.9 - 2026-07-04
1327
1498
 
1328
1499
  ### Fixed
1500
+
1329
1501
  - **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
1502
  - **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
1503
  - **UTF-8 locale for pod-triggering spawns** — CocoaPods no longer dies on multibyte podspecs under locale-less orchestrator environments.
1332
1504
  - **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
1505
 
1334
1506
  ### Added
1507
+
1335
1508
  - **`mm-harness stop`** — stop the port-scoped Metro this checkout owns and close its log-tail window; idempotent; teaching error on extension/core.
1336
1509
  - **Extension launch streams live** with an upfront intent line (ports named); mobile and extension long operations are never silent.
1337
1510
  - **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 +1512,7 @@ Minor release: the adapter-surface architecture plus the day's feature wave (0.3
1339
1512
  - **docs/UX-PRINCIPLES.md** — the CLI interaction contract (human surface rules; `--json` envelope purity is exempt and inviolable).
1340
1513
 
1341
1514
  ### Fixed
1515
+
1342
1516
  - **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
1517
  - **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
1518
  - **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 +1521,7 @@ Minor release: the adapter-surface architecture plus the day's feature wave (0.3
1347
1521
  - **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
1522
 
1349
1523
  ### Added
1524
+
1350
1525
  - **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
1526
  - **`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
1527
  - **`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 +1533,7 @@ Minor release: the adapter-surface architecture plus the day's feature wave (0.3
1358
1533
  - **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
1534
 
1360
1535
  ### Changed
1536
+
1361
1537
  - **`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
1538
  - **`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
1539
  - **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 +1542,56 @@ Minor release: the adapter-surface architecture plus the day's feature wave (0.3
1366
1542
  ## 0.3.8 - 2026-07-04
1367
1543
 
1368
1544
  ### Fixed
1545
+
1369
1546
  - **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
1547
 
1371
1548
  ## 0.3.7 - 2026-07-04
1372
1549
 
1373
1550
  ### Fixed
1551
+
1374
1552
  - **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
1553
 
1376
1554
  ## 0.3.6 - 2026-07-04
1377
1555
 
1378
1556
  ### Fixed
1557
+
1379
1558
  - **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
1559
 
1381
1560
  ## 0.3.5 - 2026-07-04
1382
1561
 
1383
1562
  ### Fixed
1563
+
1384
1564
  - **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
1565
 
1386
1566
  ### Changed
1567
+
1387
1568
  - **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
1569
 
1389
1570
  ## 0.3.4 - 2026-07-04
1390
1571
 
1391
1572
  ### Fixed
1573
+
1392
1574
  - **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
1575
  - **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
1576
 
1395
1577
  ## 0.3.3 - 2026-07-04
1396
1578
 
1397
1579
  ### Fixed
1580
+
1398
1581
  - **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
1582
  - **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
1583
 
1401
1584
  ## 0.3.2 - 2026-07-04
1402
1585
 
1403
1586
  ### Fixed
1587
+
1404
1588
  - **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
1589
  - **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
1590
 
1407
1591
  ## 0.3.1 - 2026-07-03
1408
1592
 
1409
1593
  ### Fixed
1594
+
1410
1595
  - **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
1596
  - **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
1597
  - **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 +1601,11 @@ Minor release: the adapter-surface architecture plus the day's feature wave (0.3
1416
1601
  ## 0.3.0 - 2026-07-03
1417
1602
 
1418
1603
  ### Changed
1604
+
1419
1605
  - 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
1606
 
1421
1607
  ### Fixed
1608
+
1422
1609
  - Extension `launch` opens the app directly (composes `live.sh`) instead of a retired command.
1423
1610
  - Mobile `cleanup` resilient to a stale runner-source path.
1424
1611
  - Core run teaches the correct next step when no account is set.