@deeeed/metamask-harness 0.17.0 → 0.17.2

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 (106) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/README.md +90 -140
  3. package/adapters/core/cleanup.sh +0 -0
  4. package/adapters/core/inject.sh +0 -0
  5. package/adapters/extension/cleanup.mjs +0 -0
  6. package/adapters/extension/console-tail.mjs +51 -24
  7. package/adapters/extension/ensure-browser.sh +0 -0
  8. package/adapters/extension/inject.mjs +5 -9
  9. package/adapters/extension/launch-browser.cjs +0 -0
  10. package/adapters/extension/launch-webpack.cjs +83 -0
  11. package/adapters/extension/launch.sh +8 -11
  12. package/adapters/extension/live.sh +2 -0
  13. package/adapters/extension/readiness.mjs +0 -0
  14. package/adapters/extension/reattach.sh +1 -0
  15. package/adapters/extension/refresh-build.sh +0 -0
  16. package/adapters/extension/seed-fixture.sh +4 -12
  17. package/adapters/extension/sidepanel-toggle.sh +4 -1
  18. package/adapters/extension/snapshot-dist.sh +0 -0
  19. package/adapters/extension/stamp-runtime-title.cjs +49 -0
  20. package/adapters/extension/start-watch.sh +16 -4
  21. package/adapters/extension/stop-viewers.sh +1 -1
  22. package/adapters/extension/sync-webpack-dist.cjs +105 -0
  23. package/adapters/extension/verify.sh +1 -1
  24. package/adapters/extension/wallet-fixture-state.cjs +0 -0
  25. package/adapters/manifest.json +46 -38
  26. package/adapters/mobile/bridge-runtime/cdp-bridge.cjs +0 -0
  27. package/adapters/mobile/bridge-runtime/console-forwarder.cjs +1 -1
  28. package/adapters/mobile/bridge-runtime/lib/target-discovery.cjs +2 -3
  29. package/adapters/mobile/bridge-runtime/setup-wallet.sh +0 -0
  30. package/adapters/mobile/cleanup.sh +16 -176
  31. package/adapters/mobile/inject.sh +88 -671
  32. package/adapters/mobile/launch-console-forwarder.cjs +39 -0
  33. package/adapters/mobile/lib/metro-listener.sh +0 -0
  34. package/adapters/mobile/lib/tmux-viewer.sh +0 -0
  35. package/adapters/mobile/open-device.sh +0 -0
  36. package/adapters/mobile/prewarm-bundle.sh +0 -0
  37. package/adapters/mobile/start-console-forwarder.sh +70 -0
  38. package/adapters/mobile/start-metro.sh +0 -41
  39. package/adapters/mobile/stop-metro.sh +0 -0
  40. package/adapters/mobile/verify.sh +4 -61
  41. package/adapters/mobile/wait-for-bridge.sh +0 -0
  42. package/adapters/mobile/yarn-setup.sh +0 -0
  43. package/adapters/shared/activate-repo-node.sh +0 -0
  44. package/adapters/shared/activate-repo-ruby.sh +0 -0
  45. package/adapters/shared/cli-ux.sh +0 -0
  46. package/adapters/shared/ensure-runner-deps.sh +0 -0
  47. package/adapters/shared/harness-path.sh +0 -0
  48. package/adapters/shared/hash-helpers.sh +0 -0
  49. package/adapters/shared/install-repo-deps.sh +0 -0
  50. package/adapters/shared/json-field.sh +0 -0
  51. package/adapters/shared/open-debug.mjs +90 -36
  52. package/adapters/shared/open-log-window.sh +0 -0
  53. package/adapters/shared/reap-checkout-metros.sh +0 -0
  54. package/adapters/shared/resolve-slot-ports-core.mjs +0 -6
  55. package/adapters/shared/resolve-slot-ports.mjs +0 -0
  56. package/adapters/shared/resolve-slot-ports.sh +0 -5
  57. package/adapters/shared/sync-wallet-fixture.sh +0 -0
  58. package/adapters/shared/tmux-session.sh +0 -0
  59. package/adapters/shared/tmux-viewer.sh +0 -0
  60. package/adapters/shared/update-check-worker.mjs +37 -0
  61. package/dist/adapters/extension/console-capture.js +84 -0
  62. package/dist/adapters/extension/runtime-decision.js +14 -2
  63. package/dist/adapters/extension/surface.js +3 -2
  64. package/dist/adapters/mobile/prepare.js +22 -7
  65. package/dist/adapters/mobile/surface.js +1 -1
  66. package/dist/adapters/resolve-slot-ports.js +0 -2
  67. package/dist/adapters/slot-ports.js +8 -0
  68. package/dist/cli.js +3 -11
  69. package/dist/commands/call.js +9 -4
  70. package/dist/commands/debug.js +38 -31
  71. package/dist/commands/fixtures.js +18 -10
  72. package/dist/commands/launch/index.js +10 -0
  73. package/dist/commands/logs.js +8 -6
  74. package/dist/commands/manifest.js +5 -17
  75. package/dist/commands/run-engine.js +1 -32
  76. package/dist/commands/run-report.js +1 -1
  77. package/dist/commands/run.js +50 -3
  78. package/dist/commands/status-probe.js +1 -1
  79. package/dist/commands/update.js +15 -28
  80. package/dist/doctor.js +1 -4
  81. package/dist/harness.js +5 -123
  82. package/dist/live-adapter-contract.js +6 -20
  83. package/dist/mm-harness-cli.js +6 -43
  84. package/dist/paths.js +2 -5
  85. package/dist/run-diagnostics.js +35 -40
  86. package/dist/runtime-context.js +26 -8
  87. package/docs/CHEATSHEET.md +39 -52
  88. package/docs/CLI-ERGONOMICS-HUMAN-QA.md +48 -104
  89. package/docs/CODE-MAP.md +1 -1
  90. package/docs/VIDEO-DEMO-VALIDATION.md +62 -0
  91. package/docs/architecture.md +65 -375
  92. package/docs/live-adapter-contract.md +3 -1
  93. package/library/actions/core/perps/_controller.mjs +10 -55
  94. package/library/manifests/mobile.action-manifest.json +1 -1
  95. package/package.json +2 -2
  96. package/scripts/completions.sh +0 -0
  97. package/scripts/install-completions.sh +0 -0
  98. package/adapters/mobile/overlay/app/dev-tools/AgenticService/AgentStepHud.tsx.patch +0 -185
  99. package/adapters/mobile/overlay/app/dev-tools/AgenticService/AgenticService.ts.patch +0 -1662
  100. package/adapters/mobile/overlay/compat/README.md +0 -28
  101. package/adapters/mobile/overlay/compat/rn81-message-event-source.patch +0 -42
  102. package/adapters/shared/resolve-farmslot-ports-core.mjs +0 -3
  103. package/adapters/shared/resolve-farmslot-ports.mjs +0 -5
  104. package/adapters/shared/resolve-farmslot-ports.sh +0 -7
  105. package/docs/CLI-SPEC.md +0 -1142
  106. package/docs/MENTAL-MODEL.md +0 -295
package/docs/CLI-SPEC.md DELETED
@@ -1,1142 +0,0 @@
1
- # mm-harness — CLI Specification (spec-first)
2
-
3
- The detailed contract beneath [MENTAL-MODEL.md](MENTAL-MODEL.md) (skim that first
4
- for the before→after at a glance). This document defines every capability: Part 1
5
- inventories everything today; Part 2 specs each end-state verb in full; Part 3
6
- audits every caller; Part 4 audits every env var. Each verb is fully specified here
7
- independent of its implementation status.
8
-
9
- **`mm-harness` is built for the agent but easy for the human to use and
10
- understand.** The agent gets full depth (explicit flags, `--json`, ports, build
11
- tiers — machine-grade determinism); the human gets short verbs, positional
12
- targets, good defaults, completions, and teaching help/errors. Every table's
13
- human-form vs agent-form split is the direct expression of that sentence.
14
-
15
- Naming: **"harness" is the tool** (`mm-harness`); the engine is
16
- `@farmslot/recipe-harness`; the injected files are the **runtime overlay**.
17
-
18
- **Clean break (no aliases).** End state has exactly one command, `mm-harness`.
19
- The `metamask-recipe` / `mm-recipe` / `mme-recipe` names and the `harness <verb>`
20
- subcommand form are REMOVED in the same coordinated wave (Part 3). The draft code
21
- keeps the old bins working so the tree still builds/tests; the surface documented
22
- here is end-state only.
23
-
24
- ---
25
-
26
- # Part 1 — Complete inventory of existing capabilities
27
-
28
- **Why this exists (Arthur):** to *guarantee no capability is lost* in the
29
- refactor. **Acceptance rule:** every row MUST have a fate, and the fates are
30
- exhaustive —
31
-
32
- - **ROUTES-NOW** — capability works via `mm-harness` today (this draft),
33
- - **STUB** — spec'd here, coming (exits 2 with a teaching message today),
34
- - **ABSORB-LATER** — capability folds into an `mm-harness` verb (spec'd in Part 2 with a maps-to row),
35
- - **KEEP-INTERNAL** — still reachable, just not a user-facing verb,
36
- - **REMOVE** — name/bin/subcommand deleted in the wave; **requires a one-line justification** (needs Arthur's sign-off).
37
-
38
- **No row may be unmapped.** An unmapped row means the refactor is not allowed to
39
- proceed. Source of truth: the bins' `case` statements + the naming audit.
40
- Every row is real.
41
-
42
- ## Surface A — `metamask-recipe` typed CLI (`runner/src/cli.ts`)
43
-
44
- Name `metamask-recipe` → **REMOVE** (becomes `mm-harness`; no alias). Capability rows:
45
-
46
- | Verb | What it does today | Platforms | Class | Fate |
47
- |---|---|---|---|---|
48
- | `manifest` | Print/validate an action manifest | mobile/extension/core | ADVANCED | **REMOVE** — 3 functions rehome: (1) well-formedness validation → `doctor` readiness check; (2) raw protocol dump → `actions --raw` flag; (3) manifest path + protocol version → `doctor` explain-my-setup section. "action manifest" stays as the FILE name in docs; only manifest EDITORS needed this verb (maintainer/CI audience, served by `doctor` + `--raw`). |
49
- | `actions` | Describe declared actions | all | DISCOVER | ROUTES-NOW |
50
- | `doctor` | Readiness check (no launch) | all | PROVE | ROUTES-NOW |
51
- | `runtime-health` | Probe extension CDP runtime health | extension | advanced | **KEEP-INTERNAL** — becomes internal function inside `launch`/`verify` self-healing path |
52
- | `runtime-decision` | Decide readiness/rebuild need | extension/mobile | advanced | **KEEP-INTERNAL** — becomes internal function inside `launch` self-healing path |
53
- | `runtime-launch` | Launch extension runtime via live.sh | extension | advanced | **KEEP-INTERNAL** — becomes internal function inside `launch` self-healing path |
54
- | `resolve-extension` | Resolve the extension id | extension | advanced | **KEEP-INTERNAL** — internal plumbing; called inside `launch`/`launch --verify` |
55
- | `ensure-ready` | Ensure extension ready (close home tabs) | extension | advanced | **KEEP-INTERNAL** — becomes internal function inside `launch`/`verify` self-healing path |
56
- | `run` | Run a recipe, write evidence | all | PROVE | ROUTES-NOW |
57
- | `check diff` | Run bounded repo-local validation for the active git diff | all | PROVE | ROUTES-NOW |
58
- | `self-test` | Package-wiring smoke (no device) | mobile+extension | advanced | **KEEP-INTERNAL** — CI smoke test; callable via `package.json` scripts; hidden from `--help` |
59
- | `flows` | List/promote library flows | all | DISCOVER | ROUTES-NOW |
60
- | `harness install\|verify\|cleanup\|live` | Runtime overlay lifecycle | all (core: no live) | OVERLAY | ROUTES-NOW (`harness` prefix → top-level); `live` **REMOVE** — dissolved into `launch --verify` |
61
- | `mobile <...>` | Passthrough to `mm-recipe` | mobile | passthrough | ABSORB-LATER; passthrough REMOVE |
62
- | `extension <...>` | Passthrough to `mme-recipe` | extension | passthrough | ABSORB-LATER; passthrough REMOVE |
63
-
64
- ## Surface B — `mm-harness` verbs (this draft)
65
-
66
- | Verb | What it does today | Platforms | Class | Fate |
67
- |---|---|---|---|---|
68
- | `install\|verify\|cleanup` (top-level) | Overlay lifecycle (final home; `live` dissolved → `launch --verify`) | all | OVERLAY | ROUTES-NOW |
69
- | `live` | Overlay install + launch + live-verify | all (core: n/a) | OVERLAY | **REMOVE** — dissolved into `launch --verify` (same sequence: install overlay if missing → launch → CDP poll → smoke verify; zero capability lost — `launch --verify` never seeds fixtures) |
70
- | `launch` | Quick relaunch / `--build` / `--verify`; auto-ensure overlay + heal | mobile/extension (core: teaching error) | DAILY LOOP | STUB → **REAL (wave 3)** |
71
- | `logs` | Tail Metro/webpack logs (composes porcelain `logs`) | mobile/extension | DAILY LOOP | STUB → **REAL (wave 3)** |
72
- | `debug` | Open DevTools / RN dev menu (composes porcelain `debug`/`dev-menu`) | mobile/extension | DAILY LOOP | STUB → **REAL (wave 3)** |
73
- | `fixtures` | `sync` files / `set` the wallet (wallet DATA only) | mobile/extension | DAILY LOOP | STUB → **REAL (wave 3)** |
74
- | `validate` | Dissolved into `run` (`run` validates first; `run --plan` = plan-only, no device) — `run --plan` **REAL as of wave 2** | all | DISCOVER | **REMOVE** — zero capability lost; rehomed into `run` |
75
- | `call` | Ad-hoc single-action execution via the real engine path (one-node recipe) — **REAL as of wave 2** (was STUB in wave 1) | all | DISCOVER | STUB → **REAL (wave 2)** |
76
-
77
- ## Surface C — `bin/mm-recipe` porcelain (mobile) — name REMOVE
78
-
79
- | Verb | What it does today | Class | Fate |
80
- |---|---|---|---|
81
- | `interactive\|menu` | Interactive TUI menu | setup | ABSORB-LATER |
82
- | `ports` | Show slot ports/paths | advanced | ABSORB-LATER |
83
- | `up` | Decision-aware full lifecycle to ready | daily | ABSORB-LATER |
84
- | `decision` | Print readiness decision | advanced | ABSORB-LATER |
85
- | `sync` | Sync runtime overlay + fixtures | daily | ABSORB-LATER |
86
- | `completion\|complete` | Shell completion script | tooling | KEEP-INTERNAL |
87
- | `prepare` | Prepare runtime (install/build) | setup | ABSORB-LATER |
88
- | `runtime-status` | Runtime status JSON | advanced | ABSORB-LATER |
89
- | `ios\|start` | Launch iOS dev client | daily | ABSORB-LATER |
90
- | `android` | Launch Android dev client | daily | ABSORB-LATER |
91
- | `setup:ios` | Launch iOS + apply wallet fixture | daily | ABSORB-LATER |
92
- | `setup:android` | Launch Android + apply wallet fixture | daily | ABSORB-LATER |
93
- | `refresh\|reload\|relaunch` | Reload running app | daily | ABSORB-LATER |
94
- | `logs\|tail` | Tail Metro log (`--full` = raw) | daily | ABSORB-LATER |
95
- | `status\|home` | Status dashboard + next commands | daily | ABSORB-LATER |
96
- | `app-status\|bridge-status` | Bridge status via `__AGENTIC__` | advanced | ABSORB-LATER |
97
- | `route\|get-route` | Current route from bridge | advanced | ABSORB-LATER |
98
- | `navigate` | Navigate to a route via bridge | advanced | ABSORB-LATER |
99
- | `back\|go-back` | Go back via bridge | advanced | ABSORB-LATER |
100
- | `unlock` | Unlock wallet with fixture password | daily | ABSORB-LATER |
101
- | `setup-wallet\|wallet-setup` | Apply wallet fixture (SRP/pw/accounts) | daily | ABSORB-LATER |
102
- | `accounts\|list-accounts` | List accounts via bridge | advanced | ABSORB-LATER |
103
- | `select-account\|switch-account` | Switch account via bridge | advanced | ABSORB-LATER |
104
- | `screenshot` | Capture app screenshot | advanced | ABSORB-LATER |
105
- | `debug` | Open RN DevTools / Hermes inspector | daily | ABSORB-LATER |
106
- | `dev-menu\|devmenu\|menu` | Open RN developer menu | daily | ABSORB-LATER |
107
- | `stop` | Stop Metro | setup | ABSORB-LATER |
108
- | `actions` | Actions (delegates to typed CLI) | PROVE | ABSORB-LATER |
109
- | `doctor` | Doctor (delegates to typed CLI) | PROVE | ABSORB-LATER |
110
- | `run` | Run a recipe (auto adapter+artifacts) | PROVE | ABSORB-LATER |
111
-
112
- ## Surface D — `bin/mme-recipe` porcelain (extension) — name REMOVE
113
-
114
- | Verb | What it does today | Class | Fate |
115
- |---|---|---|---|
116
- | `interactive\|menu` | Interactive TUI menu | setup | ABSORB-LATER |
117
- | `sync` | Sync runtime overlay | daily | ABSORB-LATER |
118
- | `completion\|complete` | Shell completion script | tooling | KEEP-INTERNAL |
119
- | `prepare` | Prepare runtime (install/build) | setup | ABSORB-LATER |
120
- | `status\|health\|home` | Status dashboard | daily | ABSORB-LATER |
121
- | `ports` | Show slot ports/paths | advanced | ABSORB-LATER |
122
- | `up` | Full build + launch + fixture | daily | ABSORB-LATER |
123
- | `runtime-status` | Runtime status JSON | advanced | ABSORB-LATER |
124
- | `decision\|decide` | Print readiness decision | advanced | ABSORB-LATER |
125
- | `update\|sync-runtime` | Hot-swap runtime overlay | daily | ABSORB-LATER |
126
- | `ready\|ensure-ready` | Wait for/ensure runtime ready | daily | ABSORB-LATER |
127
- | `watch\|start-watch\|watcher` | Start webpack watcher (background) | daily | ABSORB-LATER |
128
- | `stop\|stop-watch\|stop-watcher` | Stop watcher | setup | ABSORB-LATER |
129
- | `logs\|tail\|tail-webpack` | Tail webpack log | daily | ABSORB-LATER |
130
- | `rebuild\|reset\|reopen\|reload\|browser\|launch\|full-launch\|runtime-launch` | Quick relaunch (snapshot dist + reopen Chrome) | daily | ABSORB-LATER |
131
- | `refresh` | Incremental refresh | daily | ABSORB-LATER |
132
- | `build` | Incremental rebuild | daily | ABSORB-LATER |
133
- | `refresh:once\|refresh-once\|build:once\|build-once` | One-shot rebuild | daily | ABSORB-LATER |
134
- | `debug\|devtools\|inspect` | Open Chrome DevTools (`worker` = SW) | daily | ABSORB-LATER |
135
- | `sidepanel` | Toggle popup ↔ sidebar via CDP | daily | ABSORB-LATER |
136
- | `actions` | Actions (delegates to typed CLI) | PROVE | ABSORB-LATER |
137
- | `doctor` | Doctor (delegates to typed CLI) | PROVE | ABSORB-LATER |
138
- | `run` | Run a recipe (auto adapter+artifacts) | PROVE | ABSORB-LATER |
139
-
140
- ## Surface E — `recipe-harness` skill
141
-
142
- | Verb | What it does today | Class | Fate |
143
- |---|---|---|---|
144
- | `install\|verify\|cleanup\|live` | ~812 lines bash: resolve runner source + dispatch overlay lifecycle | OVERLAY | **REMOVE** — superseded by `mm-harness install/verify/cleanup` + `launch --verify`; skill thins to a caller |
145
-
146
- ## Surface F — Orchestration entry points (invoked by the overlay verbs)
147
-
148
- | Entry | What it does today | Class | Fate |
149
- |---|---|---|---|
150
- | `{mobile,core}/inject.sh`, `extension/inject.mjs` | Install overlay into `temp/recipe/harness/<adapter>/` | OVERLAY impl | KEEP-INTERNAL (via `mm-harness install`) |
151
- | `{mobile,core}/cleanup.sh`, `extension/cleanup.mjs` | Remove overlay | OVERLAY impl | KEEP-INTERNAL (via `mm-harness cleanup`) |
152
- | `{mobile,extension}/live.sh` | Launch + live-verify | OVERLAY impl | KEEP-INTERNAL (via `mm-harness launch --verify`) |
153
- | `runner/{mobile,extension}/verify.sh` (+ installed `scripts/verify.sh`) | Verify overlay/runtime health | OVERLAY impl | KEEP-INTERNAL (via `mm-harness verify`) |
154
- | `orchestration/doctor.mjs` | Orchestration manifest doctor | advanced impl | KEEP-INTERNAL |
155
-
156
- ## Surface G — Shell completion
157
-
158
- | Artifact | What it does today | Fate |
159
- |---|---|---|
160
- | `completions/_recipe` (zsh `#compdef recipe mme-recipe mm-recipe`) | Completes the three legacy names + adapters/recipes/flags | **REMOVE** — replaced by bundled `mm-harness` completions (zsh + bash), auto-installed on setup |
161
-
162
- ## Completeness check
163
-
164
- - **Total rows:** 81 (A:14 · B:7 · C:30 · D:23 · E:1 · F:5 · G:1)
165
- - ROUTES-NOW: 6 · STUB: 5 · ABSORB-LATER: 53 · KEEP-INTERNAL: 13 · REMOVE: 4 (total 82 rows — live split into own row in Surface B)
166
- - 6 + 5 + 53 + 13 + 4 = 81 rows of capability (live row is REMOVE, not a net-new capability)
167
- - **Unmapped: 0** ✅ (REMOVE rows: `manifest` A; `live` B dissolved → `launch --verify`; `validate` B dissolved → `run`/`run --plan`; Surface E; Surface G; ADVANCED KEEP-INTERNAL; name-level REMOVE in bin table + Part 3)
168
- - **Class breakdown:** DAILY LOOP: 4 STUB → **REAL (wave 3)** · DISCOVER: 2 ROUTES-NOW + 1 STUB→REAL (`call`, wave 2) + 1 REMOVE (`validate` dissolved) · PROVE: 3 ROUTES-NOW · OVERLAY: 3 ROUTES-NOW + 1 REMOVE (`live` dissolved → `launch --verify`) · ADVANCED: 6 KEEP-INTERNAL + 1 REMOVE (`manifest`) · passthrough/tooling: rest
169
-
170
- ---
171
-
172
- # Part 2 — Per-verb capability spec (end state)
173
-
174
- Grammar: `mm-harness <verb> [target] [flags]`. OPTIONS tables carry an **ENV**
175
- column — resolution order is **flag > env > config/fixture > built-in default**.
176
- The human form never needs an env prefix (`VAR=x mm-harness …` is agent/CI only).
177
- Audience: **human** (daily, ≤1 flag), **agent** (depth), **both**.
178
-
179
- > Scope note: `evidence` / `quality` / `review` verbs from the wider DESIGN-mm-harness
180
- > map are **not** in this repo yet and out of scope; evidence is produced by `run`.
181
-
182
- ## `launch` (REAL as of wave 3 → main DAILY LOOP verb)
183
-
184
- > **Implemented (wave 3):** `handleLaunch` in `src/commands/launch/`. The typed
185
- > layer owns grammar (mobile-target mandatory, core/extension refusals), tier
186
- > selection, quick-launch change detection, overlay auto-ensure, healing policy +
187
- > bounds, teaching errors, and the `--json` contract. It does NOT rewrite
188
- > orchestration: it COMPOSES the same porcelain paths (grounded in bin/mm-recipe +
189
- > bin/mme-recipe case arms) and, for `--verify`, the overlay `live` sequence via
190
- > `handleHarness`. Composition seams `MM_HARNESS_MM_RECIPE_BIN` /
191
- > `MM_HARNESS_MME_RECIPE_BIN` / `MM_HARNESS_INSTALL_BIN` make every path
192
- > contract-testable without a device (`tests/contract/mm-harness-wave3.test.sh`).
193
- > Real-device boot + `--verify`/`--runway`/`--sidepanel` toggle land in the Tier B
194
- > on-device checklist.
195
-
196
- **Synopsis:**
197
- - Mobile: `mm-harness launch <ios|android> [flags]` — **target is MANDATORY**; missing target → teaching error: "specify ios or android"
198
- - Extension: `mm-harness launch [flags]` — target omitted; display mode via `--sidepanel` | `--fullscreen` (default `--fullscreen`)
199
-
200
- Quick relaunch by default. `--build` triggers a full native/webpack build first.
201
-
202
- **Per-platform behavior:**
203
-
204
- | Platform | Behavior |
205
- |---|---|
206
- | extension | Snapshot `dist/chrome`, open Chrome for Testing over CDP (never seeds the wallet fixture — first-run wallet is `fixtures set`). `--build` webpack-builds first; `--watch` starts the persistent watcher; `--sidepanel` opens sidepanel mode (default `--fullscreen`). |
207
- | mobile (ios\|android) | Start/reuse Metro, prewarm bundle, open iOS or Android dev client. `--build` = decision-aware native rebuild; default quick tier never triggers a native build. `--runway` runs a runway check after launch (mobile only; teaching error on extension). |
208
- | core | n/a — headless. Exit 2: "core is headless; use `mm-harness verify`". |
209
-
210
- **Options:**
211
-
212
- | Flag | Type | Default | ENV (agent) | Audience | Description |
213
- |---|---|---|---|---|---|
214
- | `<ios\|android>` positional | ios\|android | **required on mobile** | — | human | Target platform — mandatory; teaching error if omitted |
215
- | `--platform <p>` | mobile\|extension\|ios\|android | = positional / auto | `PLATFORM` | agent | Explicit platform (agent form of the positional) |
216
- | `--device <udid\|name>` | string | first booted sim | `IOS_SIMULATOR` / `ADB_SERIAL` | both | Target simulator/device |
217
- | `--build` | bool | false (quick) | `MOBILE_PREFLIGHT_MODE` | both | Full native/webpack build tier (replaces quick relaunch) |
218
- | `--watch` | bool | false | — | agent | Persistent webpack watcher then relaunch (extension only) |
219
- | `--verify` | bool | false | — | both | After launch: poll CDP/bridge until ready + run smoke verify (implements the retired `live` sequence: install overlay if missing → launch → poll → verify — never seeds fixtures; a first-run that needs a seeded wallet errors and points at `fixtures set`) |
220
- | `--sidepanel` | bool | false | — | human | Extension: open in sidepanel mode |
221
- | `--fullscreen` | bool | true | — | human | Extension: open fullscreen (default; explicit override of --sidepanel) |
222
- | `--runway` | bool | false | — | both | Post-launch runway check (mobile only; teaching error on extension: "runway is mobile-only") |
223
- | `--cdp-port <port>` | number | slot/context port | `CDP_PORT`/`RECIPE_CDP_PORT` | agent | Extension CDP port |
224
- | `--heal` | off\|infra-only\|auto | auto | — | both | Healing policy (see Part 5 §5.7). `auto` = auto-ensure overlay + heal transport (Metro/Chrome/CDP/tabs) silently; NEVER touches wallet state. `infra-only` = heal transport only. `off` = repro-preserving, no healing, also disables auto-install (the only opt-out). Bounds are ALWAYS enforced (recipe running / app-logic failure / same recovery already failed once → `recoverable:false` in `--json`); no flag bypasses them. `--yes` confirms prompts non-interactively. |
225
- | `--json` | bool | false | — | agent | Machine-readable summary; includes `recovered[]` (stable codes), `mutations[]`, `phase`, `schemaVersion` |
226
- | `--json-stream` | bool | false | — | agent | JSONL event stream on stdout (one event per line; see Part 5 §5.5) |
227
-
228
- **Auto-ensure overlay:** `launch` checks for the runtime overlay before launching (phase: `install`). If missing: installs it inline, then proceeds. **First-install overlay notice:** on the very first install for this checkout, prints `installed mm-harness overlay v<version> → <path>` to **stderr** in human mode; in `--json` it appears solely as `mutations[]` (stdout stays clean for machine output). Use `--heal=off` to disable all auto-install and healing (repro-preserving; use when `mm-harness install` runs explicitly upstream in CI).
229
-
230
- **Self-healing (idempotent, controlled by `--heal`):** `launch` detects broken runtime state and recovers — the agent's recovery command is simply re-running `mm-harness launch <target>`. It does NOT silently ignore a bad state; it makes reality match intent:
231
-
232
- | Platform | Detected broken state | Recovery action (internalized) |
233
- |---|---|---|
234
- | extension | Chrome closed / CDP offline / stale home tabs | reopen Chrome → CDP poll → close stale tabs (was: `runtime-launch` + `ensure-ready` + `runtime-health`) — never touches the wallet fixture |
235
- | mobile | Metro dead / dev client disconnected / bridge offline | Restart Metro → prewarm bundle → relaunch dev client (was: `runtime-decision` + `runtime-launch`) |
236
-
237
- Recovery is silent in human mode. With `--json`: `"recovered": ["metro.restarted", "tabs.cleared"]` (stable codes — see Part 5 §5.2). `recoverable:false` + `attemptedRecoveries` stop agent retry loops when bounds are hit. `--heal=off` disables all recovery (fail fast, exact broken state preserved).
238
-
239
- **Quick-launch change detection:** Before a quick launch (no `--build`), `launch` checks whether native build inputs (iOS Podfile.lock, Gradle wrapper, `package.json` native deps) or webpack config have changed since the last build using the farm layer's native-diff heuristic. If changed, refuses quick launch with a teaching error: `✗ native build inputs changed since last build — run: mm-harness launch <target> --build`. Never silently runs on a stale build.
240
-
241
- **Exit codes:** 0 launched · 1 launch failed · 2 bad args / core / missing mobile target · 3 infra failure not auto-healed (healing `off` or a bound was hit) · 4 bounded/not-retryable (see Part 5 §5.6).
242
- **Failure (teaching):** `✗ launch <platform> failed\n <cause>\n Next: <exact command>`.
243
- **Missing mobile target:** `✗ mm-harness launch: target is required for mobile.\n Run: mm-harness launch ios or mm-harness launch android`.
244
- **Outputs:** running app + Metro/watcher; launch summary/logs under the slot runtime dir; `--json` status object.
245
- **Maps-to:** C:`ios\|start`,`android`,`up`,`refresh…`,`setup:ios/android`(launch half); D:`rebuild…`,`up`,`watch`+`rebuild`,`build`,`refresh*`,`sidepanel`.
246
-
247
- ## `logs` (REAL as of wave 3 → main DAILY LOOP verb)
248
-
249
- > **Implemented (wave 3):** `handleLogs` in `runner/src/commands/logs.ts` — composes
250
- > the porcelain `logs` (+ `--full`). Teaching error (exit 2) when nothing is
251
- > running (no `metro.log`/`webpack.log` for the checkout), pointing at `launch`.
252
-
253
- **Synopsis:** `mm-harness logs [flags]` — tail Metro/webpack + app logs.
254
-
255
- | Flag | Type | Default | ENV (agent) | Audience | Description |
256
- |---|---|---|---|---|---|
257
- | `--full` | bool | false (compact) | `RECIPE_LOG_UI` | both | Raw log tail |
258
- | `--events <n>` | number | 10 | `RECIPE_LOG_EVENTS` | agent | Compact event count |
259
- | `--platform <p>` | mobile\|extension | auto | `PLATFORM` | agent | Force platform |
260
- | `--json` | bool | false | — | agent | Structured events |
261
-
262
- **Exit:** 0 · 2 bad args. **Maps-to:** C:`logs\|tail`; D:`logs\|tail\|tail-webpack`.
263
-
264
- ## `debug` (REAL as of wave 3 → main DAILY LOOP verb)
265
-
266
- > **Implemented (wave 3):** `handleDebug` in `runner/src/commands/debug.ts` — extension
267
- > composes `debug` (`--worker` → `debug worker`); mobile composes `debug`
268
- > (`--dev-menu` → `dev-menu`). Cross-platform flag misuse teaches (exit 2).
269
-
270
- **Synopsis:** `mm-harness debug [flags]` — open the debug console.
271
-
272
- | Flag | Type | Default | ENV | Audience | Description |
273
- |---|---|---|---|---|---|
274
- | `--worker` | bool | false | — | both | Extension service-worker DevTools |
275
- | `--dev-menu` | bool | false | — | both | Mobile RN developer menu (folds `dev-menu` into a flag) |
276
- | `--platform <p>` | mobile\|extension | auto | `PLATFORM` | agent | Force platform |
277
-
278
- **Exit:** 0 · 2 bad args. **Maps-to:** C:`debug`,`dev-menu…`; D:`debug\|devtools\|inspect`(+`worker`).
279
-
280
- ## `fixtures` (REAL as of wave 3 → main DAILY LOOP verb, with subcommands)
281
-
282
- > **Implemented (wave 3):** `handleFixtures` in `runner/src/commands/fixtures.ts`.
283
- > `sync` composes the porcelain `sync` (both adapters). `set` composes the porcelain
284
- > `setup-wallet <fixture>` on mobile; on the extension (documented GAP — no standalone
285
- > porcelain arm) it reuses `call`'s one-node engine path (`metamask.wallet.setup`,
286
- > `runOneNode` in `runner/src/cli.ts`). `set` always prints the fixture edit path;
287
- > the password is read FROM the fixture, never typed. On-device seed is Tier B.
288
-
289
- **One canonical wallet fixture per checkout** — same SSOT model as the farm installer. `init --from` copies an existing secure team/test fixture; explicit `init --dev` creates a disposable public test wallet that must never hold real funds. **Fixture status is reported by `doctor` automatically.**
290
-
291
- **Synopsis:** `mm-harness fixtures <init|sync|set|generate|finalize> [flags]` — subcommand required.
292
-
293
- | Subcommand | Behavior |
294
- |---|---|
295
- | `init` | Create the canonical fixture from `--from <path>` or explicit `--dev`; refuse replacement unless `--force` is supplied |
296
- | `sync` | Refresh **wallet fixture files** on the target (wallet DATA only — overlay refresh is `--heal` / `doctor --fix` territory, NOT `fixtures`; they are separate concerns) |
297
- | `set` | Apply THE canonical fixture (SRP/password/accounts) to Mobile/Extension; Core validates and reads the same fixture directly for signing |
298
- | `generate` | Render the extension `fixture-state.json` from a wallet fixture for pre-launch profile prefill (fronts `adapters/extension/wallet-fixture-state.cjs generate`). **Extension-only** (mobile/core teach). Requires `--fixture <wallet-fixture.json>` and `--out <fixture-state.json>`. This is the public surface for farm packs that need the pre-launch fixture-state file, replacing direct reach-in to the internal leaf. |
299
- | `finalize` | Seed account labels/selection into the **live** extension over CDP and validate import parity (fronts `adapters/extension/wallet-fixture-state.cjs seed-cdp`). **Extension-only, post-launch** (mobile/core teach). Requires `--fixture`, `--state`, `--cdp-port`, `--extension-dir`. This is the public surface for farm packs that finalize account labels after launch, replacing the last direct reach-in to the internal leaf. |
300
-
301
- | Flag | Type | Default | ENV (agent) | Audience | Description |
302
- |---|---|---|---|---|---|
303
- | `--from <json>` | path | — | — | both | `init`: copy an existing fixture without exposing its values in arguments |
304
- | `--dev` | bool | false | — | both | `init`: create a disposable public test wallet; never real funds |
305
- | `--force` | bool | false | — | both | `init`: intentionally replace the canonical fixture |
306
- | `--fixture <json>` | path | slot's `wallet-fixture.json` (`set`/`sync`); **required** for `generate`/`finalize` (input wallet fixture) | `RECIPE_WALLET_FIXTURE` | **agent** (`set`/`sync`); both (`generate`/`finalize`) | `set`/`sync`: override fixture path (human form = edit the canonical file directly). `generate`/`finalize`: the input wallet fixture. |
307
- | `--out <json>` | path | `generate`: **required**; `finalize`: leaf default report path | — | both | `generate`: output `fixture-state.json`. `finalize`: optional validation report path. |
308
- | `--state <json>` | path | — (**required** for `finalize`) | — | both | `finalize` only: the pre-launch `fixture-state.json` to seed into the live wallet |
309
- | `--cdp-port <port>` | number | — (**required** for `finalize`) | — | both | `finalize` only: CDP port of the running extension |
310
- | `--extension-dir <path>` | path | — (**required** for `finalize`) | — | both | `finalize` only: the loaded extension dist (e.g. `dist/chrome`) |
311
- | `--extension-id-file <path>` | path | — | — | agent | `finalize` only: file to read/write the resolved extension id |
312
- | `--platform <p>` | mobile\|extension | auto | `PLATFORM` | agent | Force platform |
313
-
314
- **Exit:** 0 · 1 apply/generate/finalize failed · 2 bad args (incl. missing required flags, or `generate`/`finalize` on a non-extension adapter).
315
- **`set` teaching output:** always ends with: `Wallet fixture applied. Want different accounts? Edit: <absolute-path>/wallet-fixture.json`.
316
- **`generate` --json:** `{ schemaVersion, command:"fixtures", action:"generate", adapter:"extension", fixture, out, status, exitCode, error }` — leaf output stays on stderr; stdout carries only the envelope.
317
- **`finalize` --json:** `{ schemaVersion, command:"fixtures", action:"finalize", adapter:"extension", fixture, state, cdpPort, extensionDir, out, status, exitCode, error }` — leaf progress streams to stderr; stdout carries only the envelope. A missing packed leaf yields `error.code:"LEAF_MISSING"` (exit 1), matching `generate`.
318
- **Maps-to:** `sync` ← C:`sync`, D:`sync`,`update\|sync-runtime`; `set` ← C:`setup-wallet\|wallet-setup`,`unlock`,`setup:ios/android`(wallet half); `generate` ← Extension farm `setup/generate-fixture.cjs` (retires the pack's internal-leaf resolver); `finalize` ← Extension farm `setup/preflight.sh` seed-cdp finalization (retires the pack's last `--resolve-script` reach-in).
319
- **Extension `set`:** [GAP] no standalone path today — the first-run wallet is seeded by the legacy `mme-recipe up`; the end-state home is `fixtures set` (launch / `launch --verify` never seed).
320
-
321
-
322
- ## `provision` (REAL → thin mobile setup path)
323
-
324
- **Synopsis:** `mm-harness provision [runway ios] [flags]`
325
-
326
- Installs the cached Runway iOS dev client onto a prepared mobile slot. It does not install JavaScript dependencies or start Metro; launch owns those at dispatch time. The legacy `install --runway` compatibility path maps to the same adapter surface; new farm callers should use `provision`.
327
-
328
- | Flag | Type | Default | ENV (agent) | Audience | Description |
329
- |---|---|---|---|---|---|
330
- | `--adapter <mobile\|extension\|core>` | adapter | auto | — | agent | Mobile installs Runway; extension/core teach |
331
- | `--target <path>` | path | cwd | — | agent | Slot checkout |
332
- | `--platform <ios>` | string | `ios` | `PLATFORM` | agent | Provisioned platform |
333
- | `--simulator <name\|udid>` / `--device <name\|udid>` | string | runtime context | `IOS_SIMULATOR` | agent | Target simulator |
334
- | `--runtime <id>` | string | runtime context | `IOS_RUNTIME` | agent | iOS runtime for simulator creation |
335
- | `--device-type <id>` | string | runtime context | `IOS_DEVICE_TYPE` | agent | Device type for simulator creation |
336
- | `--branch <ref>` / `--default-branch <ref>` / `--run <id>` | string | git branch / main | — | agent | Runway artifact selection |
337
- | `--cache-root <dir>` | path | XDG cache | — | agent | Shared artifact cache |
338
- | `--slot <id>` | string | runtime context | — | agent | Farm slot id recorded in the provision baseline |
339
- | `--watcher-port <port>` | number | runtime context | `WATCHER_PORT`/`METRO_PORT` | agent | Metro/watcher port carried through context and rerun hints |
340
- | `--runtime-dir <dir>` | relative path | `temp/recipe/runtime` | `RECIPE_RUNTIME_DIR` | agent | Runtime directory containing `agentic-runtime.json` and receiving `runway-provision.json` |
341
- | `--force` | bool | false | — | agent | Reinstall even when the bundle is already present |
342
- | `--resolve-only` | bool | false | — | agent | Resolve run/revision/artifact metadata only; no simulator creation, cache write, download, install, or baseline write |
343
- | `--json` | bool | false | — | agent | Machine envelope on stdout; progress/errors on stderr |
344
-
345
- ## `run` (ROUTES-NOW → main PROVE verb)
346
-
347
- **Synopsis:** `mm-harness run <recipe.json> [flags]` — validate (adapter-aware) + execute a recipe, write evidence.
348
-
349
- | Flag | Type | Default | ENV (agent) | Audience | Description |
350
- |---|---|---|---|---|---|
351
- | `--adapter <p>` | mobile\|extension\|core | auto-detect (target) | `RECIPE_HARNESS_PLATFORM` | both | Target adapter · [DEFAULT-GAP] required today |
352
- | `--artifacts-dir <dir>` | path | slot artifacts dir | — | both | Evidence output · [DEFAULT-GAP] required today · **confirmed** in `cli.ts:415`, `adapters.ts:253` |
353
- | `--project-root`/`--target <repo>` | path | `$PWD` | — | both | Checkout under test |
354
- | `--plan` | bool | false | — | both | **REAL as of wave 2.** Plan-only mode: validate adapter-aware (schema + action existence/platform vs the adapter manifest) and print what would happen — touches NO device, writes NO artifacts. Exit 0 = plan valid; exit 5 = plan has errors (exit 2 = recipe missing/unparseable). `--json` = `{ status, adapter, recipe, findings[], plan[] }`; each `plan[]` item carries `confidence: static\|conditional`. |
355
- | `--action-manifest <path>` | path | bundled | — | agent | Override manifest |
356
- | `--library <name=path[:…]>` | string | personal/team then canonical | `RECIPE_LIBRARY_PATH` | agent | Recipe library sources |
357
- | `--cdp-port <port>` | number | context | `CDP_PORT`/`RECIPE_CDP_PORT` | agent | Extension CDP port |
358
- | `--slot <id>` | string | context | `FARMSLOT_SLOT_ID`/`SLOT_ID` | agent | Slot id (fleet) · **confirmed** in `cli.ts:51,819` (sets both env vars) |
359
- | `--launch-existing-dist` | bool | false | — | agent | Reuse existing dist (extension) |
360
- | `--record-video[=full-run]` | enum | off | — | agent | Record full-run video · **confirmed** (`parseRecordVideoMode` in `cli.ts:249`; `proof-window` reserved future mode; extension + iOS confirmed; Android not yet implemented) |
361
- | `--heal` | off\|infra-only\|auto | infra-only | — | both | Healing policy (see Part 5 §5.7). Default `infra-only` — heals overlay/runtime transport but NEVER touches wallet state. Use `off` for repro-preserving behavior. Bounds are always enforced. If the failure requires a wallet write → the verb stops with `recoverable:false` exit 4 and `userAction: "run mm-harness fixtures set"`. |
362
- | `--json` | bool | false | — | agent | Machine-readable result; includes `recovered[]` (stable codes), `mutations[]`, `plan[]` (with `--plan`), `schemaVersion` |
363
- | `--json-stream` | bool | false | — | agent | JSONL event stream on stdout (one event per line; see Part 5 §5.5) |
364
-
365
- **Pacing/slowMo:** No step-level pacing or slowMo flag exists on `run`. All CDP settle delays are hardcoded within orchestration scripts. `--settle-ms` exists only in `orchestration/extension/sidepanel-toggle.sh` (internal sidepanel CDP settle, not exposed on `run`). **[GAP — pacing option candidate]**: a `--step-delay-ms` or `--settle-ms` flag on `run` would let agents/CI slow down recipe execution for flaky-step debugging; not implemented today.
366
-
367
- **Validates first (adapter-aware):** Before touching any device, `run` validates the recipe: action existence in the adapter's manifest, platform support for each action, and fixture preconditions. Validation errors exit 5 with a structured error list. `--plan` stops here — prints the plan and exits, no device touched. Without `--plan`, validation failures are fatal before any execution begins.
368
-
369
- **Run-scoped diagnostics:** Mobile combines its bounded in-app issue buffer with application-log bytes appended while the recipe executes; Extension owns a CDP console subscription for the run. The bounded, redacted result is written to `diagnostics.json`, indexed as a diagnostic artifact, and summarized as non-blocking `sideFindings`; it never changes recipe pass/fail and does not claim causality. Core is headless, so this is N/A.
370
-
371
- > **Wave-2 status:** adapter-aware validation (`validateRecipeDocument` +
372
- > `validateRecipeWithManifest`, shared helper `validateRecipeAdapterAware`) is
373
- > wired for **`run --plan`** and **`call`** at the pinned deps (`@farmslot/protocol`
374
- > `validateRecipeWithManifest` is available today). Two extensions land when
375
- > `@farmslot/recipe-harness >= 0.3.3` publishes: (1) `externalFlowIds` for recipes
376
- > that reference library flows by `call.ref` (needs `resolveRecipeLibrarySources`),
377
- > and (2) making the `run` **execute** path always-validate with those external ids.
378
- > Until then, self-contained recipes (all in-repo recipes + every one-node `call`
379
- > recipe) validate fully; the execute path relies on the engine's own manifest
380
- > validation. Tagged in code (`runner/src/cli.ts` `validateRecipeAdapterAware`).
381
-
382
- **Execution phases:** `resolve → validate → install → healthcheck → execute`, with `recover` emitted only when bounded recovery actually runs. `--json-stream` emits each transition as it occurs.
383
-
384
- **Auto-ensure overlay:** `run` checks for the runtime overlay before executing (phase: `install`). If missing: installs it inline with a one-line notice, then proceeds. **First-install overlay notice:** on the very first install for this checkout, prints `installed mm-harness overlay v<version> → <path>` to stderr in human mode; in `--json` it appears solely as `mutations[]`. Use `--heal=off` to disable all auto-install and healing.
385
-
386
- **Self-healing (idempotent, controlled by `--heal`, default `infra-only`):** Before executing the recipe, `run` checks runtime health and heals broken infra state (Metro dead → restart; CDP offline → reopen Chrome) — but NEVER touches wallet state in `infra-only` mode. The agent re-runs `mm-harness run recipe.json` to recover from any mid-session failure — no separate recovery verb needed. `--json` gains `"recovered": ["metro.restarted", ...]` (stable codes; see Part 5 §5.2). `recoverable:false` + `attemptedRecoveries[]` stop agent retry loops when bounds are hit. `--heal=off` disables all recovery (fail fast, exact broken state preserved).
387
-
388
- **Exit:** 0 pass · 1 recipe fail/error · 2 bad args · 3 infra failure not auto-healed (healing `off` / bound hit) · 4 bounded/not-retryable · 5 validation failure (adapter-aware validation found errors; same code for `run`, `run --plan`, and `call`).
389
- **Outputs:** `summary.json`, `trace.json`, `artifact-manifest.json`, screenshots/logs under `--artifacts-dir`.
390
- **Maps-to:** A:`run` (ROUTES-NOW); C/D:`run` (ABSORB-LATER — supply the auto adapter+artifacts defaults the typed CLI lacks).
391
-
392
- ## `last` (resumability inspect verb)
393
-
394
- **Synopsis:** `mm-harness last [--target <checkout>] [--runtime-dir <dir>] [--json]`
395
-
396
- Reads `last-command.json`, the atomic per-checkout journal written before and after significant runtime/proof commands. The record contains redacted args, `running|pass|fail` verdict, exit code, explicit evidence/output paths, and start/finish timestamps. A process killed between writes remains `running`, allowing an agent to inspect rather than repeat side effects. Read-only discovery commands do not replace the record, and `last` never journals itself.
397
-
398
- The file is mode `0600`; password, credential, auth, mnemonic, private-key, seed/SRP, secret, and token values are redacted before the first write. `--json` returns `{ command:"last", target, journalPath, status, exitCode, last }`. Missing or invalid state returns `LAST_NOT_FOUND` with an exact `userAction`.
399
-
400
- ## `doctor` (ROUTES-NOW → PROVE inspect verb)
401
-
402
- Readiness check for a checkout without launching the app. Doctor is the single place to understand the full slot context — **no hunting for files**. It also absorbs the retired `manifest` verb's validation function: the readiness checks include manifest well-formedness (schema valid, no unknown action references) and report the manifest path + protocol version in the explain-my-setup section. It includes three grounded sub-sections:
403
-
404
- **1. Fixture section** — grounded: `runner/src/doctor.ts` `fixtureSummary()` (line 69) returns `{ status: 'missing'|'ready'|'incomplete'|'invalid', path, accountCount, hasPassword }`, included in `createDoctorReport()` at line 125.
405
-
406
- **2. Runtime-context section (spec addition)** — reads `agentic-runtime.json` from `{target}/{runtimeDir}/agentic-runtime.json` (override: `RECIPE_RUNTIME_CONTEXT` env; grounded in `runner/src/harness.ts:206-244`) and prints every field with its current value, source, and how to customize:
407
-
408
- | Field | Env var set by harness | Flag override | How to customize |
409
- |---|---|---|---|
410
- | `slotId` | `RECIPE_SLOT_ID` | `--slot` | `doctor --fix` creates local identity; Farmslot may override |
411
- | `extensionId` | `RECIPE_HARNESS_EXTENSION_ID` | — | resolved from the built extension |
412
- | `cdpPort` | `RECIPE_CDP_PORT` / `CDP_PORT` | `--cdp-port` | `doctor --fix` claims one; flag overrides |
413
- | `runtimeStart.approved` | `RECIPE_RUNTIME_START_APPROVED` | — | edit file (`true`/`false`) |
414
- | `runtimeStart.command` | — | — | edit file |
415
- | `runtimeStart.readyUrl` | `RECIPE_RUNTIME_READY_URL` | — | edit file |
416
-
417
- **`--json` output shape** (end-state; includes both sections):
418
- ```json
419
- {
420
- "status": "pass",
421
- "adapter": "extension",
422
- "checks": [{ "id": "manifest", ... }],
423
- "fixture": { "status": "ready", "path": "temp/recipe/.../wallet-fixture.json", "accountCount": 1, "hasPassword": true },
424
- "runtimeContext": {
425
- "file": "temp/recipe/.../agentic-runtime.json",
426
- "fileExists": true,
427
- "envOverride": null,
428
- "fields": {
429
- "slotId": { "value": "slot-1", "source": "file", "envVar": "RECIPE_SLOT_ID", "customize": "doctor --fix creates local identity; Farmslot may override" },
430
- "extensionId": { "value": "abc123", "source": "file", "envVar": "RECIPE_HARNESS_EXTENSION_ID", "customize": "resolved from the built extension" },
431
- "cdpPort": { "value": "6663", "source": "file", "envVar": "CDP_PORT", "customize": "doctor --fix claims one; --cdp-port overrides" },
432
- "runtimeStart.approved": { "value": "true", "source": "file", "envVar": "RECIPE_RUNTIME_START_APPROVED", "customize": "edit file (true/false)" },
433
- "runtimeStart.command": { "value": null, "source": "default", "envVar": null, "customize": "edit file" },
434
- "runtimeStart.readyUrl": { "value": null, "source": "default", "envVar": "RECIPE_RUNTIME_READY_URL", "customize": "edit file" }
435
- }
436
- }
437
- }
438
- ```
439
-
440
- | Flag | Type | Default | ENV | Audience | Description |
441
- |---|---|---|---|---|---|
442
- | `--adapter <p>` | mobile\|extension\|core | auto-detect | `RECIPE_HARNESS_PLATFORM` | both | Target · [DEFAULT-GAP] required today |
443
- | `--target <repo>` | path | `$PWD` | — | both | Checkout to probe |
444
- | `--action-manifest <path>` | path | bundled | — | agent | Override manifest (the file; `manifest` verb is retired) |
445
- | `--fix` | bool | false | — | both | Repair overlay/runtime context and require a usable canonical fixture without launching. Exit 0 = sandbox prerequisites ready; 1 = partial/failed. JSON adds `fixed`, `failed`, and numbered `nextActions`. |
446
- | `--json` | bool | false | — | agent | Machine-readable (includes fixture + runtimeContext sections; with `--fix`: adds fixed/failed arrays) |
447
-
448
- **`--fix` framing:** `doctor --fix` runs the same healing checks as `launch`/`verify`'s self-healing path but stops short of starting the app. Use it when the agent detects a broken state and wants to repair before the next `run` — or when CI needs to repair without a launch side-effect. `doctor` without `--fix` is always pure read-only.
449
-
450
- **Exit:** 0/1.
451
- **Maps-to:** A:`doctor` (ROUTES-NOW); C/D:`doctor` (ABSORB-LATER); retired `manifest` validation → absorbed here.
452
-
453
- ---
454
-
455
- ## `check diff` (REAL → bounded local validation)
456
-
457
- **Synopsis:** `mm-harness check diff [flags]`
458
-
459
- Runs bounded validation against the target checkout's active git diff. This is
460
- for local PR readiness loops and agent templates; it does not launch an app or
461
- run a recipe.
462
-
463
- | Flag | Values | Default | Purpose |
464
- | --- | --- | --- | --- |
465
- | `--target <repo>` | path | cwd | MetaMask checkout to inspect |
466
- | `--adapter <adapter>` / `--platform <adapter>` | mobile\|extension\|core | auto-detect | Adapter label for the report |
467
- | `--base <ref>` | git ref | GitHub PR base, then remote HEAD, then `origin/develop`/`origin/main`/local fallback | Diff base |
468
- | `--profile <profile>` | `fast`\|`full` | `fast` | `fast` runs changed-file ESLint/Prettier/Jest; `full` also runs repo typecheck when available |
469
- | `--artifacts-dir <dir>` | path | `temp/recipe/check-diff/<timestamp>` | Writes summary and per-check logs |
470
- | `--json` | bool | false | Print the summary envelope to stdout |
471
-
472
- **Artifacts:** `validation-summary.json`, `validation-summary.md`, and one log
473
- per executed check.
474
-
475
- **Exit:** 0 pass · 2 bad args / not a git checkout · 5 validation failure.
476
-
477
- ---
478
-
479
- ## `--device <id>` — first-class mobile device targeting (REAL)
480
-
481
- `--device` is the uniform mobile device selector on `run`, `call`, `doctor`, and `fixtures`. Pass the **adb serial** for Android (from `adb devices`) or the **UDID / simulator name** for iOS. The harness resolves the adb serial to the Metro CDP target identity internally — users never need to know or set `ANDROID_DEVICE='Pixel 6 - 16 - API 36'`.
482
-
483
- **Internal Android identity mapping**: `ADB_SERIAL` / `ANDROID_SERIAL` carry the raw adb serial. The bridge resolves the device model via `adb -s <serial> shell getprop ro.product.model` and propagates it as `ANDROID_TARGET_DEVICE_NAME`. Target-discovery uses `ANDROID_TARGET_DEVICE_NAME` for Metro `deviceName` prefix matching (e.g. `"Pixel 6"` matches `"Pixel 6 - 16 - API 36"`). When the pinned model cannot be matched to any Metro `/json/list` candidate and multiple candidates exist, the bridge fails fast with a diagnostic listing every candidate's `deviceName` — it never silently selects the wrong device. iOS UDID/simulator name → `IOS_SIMULATOR` (unchanged).
484
-
485
- | Verb | `--device` given | `--device` omitted (mobile) |
486
- |---|---|---|
487
- | `run` / `call` | resolve id → set serial/simulator env; proceed | **ambiguity gate:** >1 connected mobile device (across both android and ios, counting only targetable ones: android state `device`, iOS state `Booted`) → fail fast (exit 2), listing connected devices and `--device <id>` hints. Exactly one targetable (or zero — existing engine errors speak) → unchanged behavior. |
488
- | `doctor` | resolve id → set env | no gate — doctor is diagnostic and **reports** the device list (`devices[]`) instead. |
489
- | `fixtures` | resolve id → set serial/simulator env before sync/set and recovery relaunch | no gate — preserves existing single-device and ambient slot behavior. |
490
-
491
- `--device` on the **extension/core** adapter is a teaching usage error (those adapters have no device to target). `run --plan` is static (touches no device) and is exempt from the gate.
492
-
493
- ## `status` (aliases `health`, `home`) — home dashboard + devices[] (REAL)
494
-
495
- `status` is the compact home dashboard for a checkout: the detected adapter, the next command to run (`next`), and — for **mobile** checkouts — a `devices[]` section with live app state. Extension/core checkouts report an empty `devices[]`. Additive envelope:
496
-
497
- ```json
498
- {
499
- "schemaVersion": 1,
500
- "command": "status",
501
- "adapter": "mobile",
502
- "target": "/path/to/checkout",
503
- "devices": [
504
- {
505
- "platform": "android", "id": "emulator-5554", "name": "Pixel_6",
506
- "state": "device", "selected": true,
507
- "currentScreen": "Wallet", "walletState": "unlocked",
508
- "selectedAccount": { "label": "Account 1", "address": "0xabcd…ef12" },
509
- "fixtureStatus": "READY"
510
- },
511
- {
512
- "platform": "ios", "id": "AAAA-BBBB", "name": "iPhone 15",
513
- "state": "Booted", "selected": false,
514
- "fixtureStatus": "READY", "liveState": "no-bridge"
515
- }
516
- ],
517
- "next": "mm-harness launch ios"
518
- }
519
- ```
520
-
521
- **Per-device live fields** (additive — absent or null when unreachable):
522
-
523
- | Field | Type | Description |
524
- |---|---|---|
525
- | `currentScreen` | string | Active route/screen in the running app (mobile bridge `status` route field) |
526
- | `walletState` | `'locked'\|'unlocked'\|'onboarding'` | Derived from bridge status: account present → unlocked; onboarding route → onboarding; else locked |
527
- | `selectedAccount` | `{ label, address }` | Active account name + address truncated to `0x1234…abcd` form |
528
- | `fixtureStatus` | `'READY'\|'missing'` | Checkout-level: whether `wallet-fixture.json` is present and well-formed |
529
- | `liveState` | `'no-bridge'\|'bridge-absent'` | Set when the bridge is unreachable/times out (`no-bridge`) or the target answers but `__AGENTIC__` is absent (`bridge-absent`); live fields omitted in both cases |
530
-
531
- **`--fast` flag:** skips all live-state probes. Output is instant (same envelope shape, live fields absent). Safe for scripts and CI where probe latency is unacceptable.
532
-
533
- **Human output** is progressive: static info (adapter, device list, next) prints immediately; a `live:` block appends after the ~2s probe window closes. `--fast` suppresses the live block entirely.
534
-
535
- `selected` is `true` when the device id matches `ADB_SERIAL`/`ANDROID_SERIAL` (android) or `IOS_SIMULATOR` (ios) env. `doctor --json` carries the same `devices[]` shape (with `selected`) for mobile checkouts.
536
-
537
- The mobile bridge probes all connected RN targets in one call and returns an array; each device is matched to its entry by name or platform-uniqueness, so multi-device checkouts (e.g. Android + iOS simulator) receive per-device live state.
538
-
539
- `liveState` in the JSON envelope (absent = fully enriched):
540
-
541
- | Value | Meaning |
542
- |---|---|
543
- | `'no-bridge'` | No matching/responding target on Metro — bridge genuinely unreachable or app not running. |
544
- | `'bridge-absent'` | Target IS reachable and answered Runtime.evaluate, but `typeof globalThis.__AGENTIC__ === 'undefined'` — the installed build predates the bridge. Reinstall a dev build to get live state. |
545
-
546
- Extension/core live-state probing (CDP home-tab route) is V1-pending — use `doctor` for runtime status.
547
-
548
- **Exit:** 0 (2 when the repo type cannot be detected).
549
-
550
- ---
551
-
552
- ## DISCOVER — agent composition input
553
-
554
- > **Decided (Arthur):** DISCOVER is a distinct group, not a sub-PROVE. The audience
555
- > moment differs — composing before running — and `do` (try one action) makes the
556
- > group's value self-evident in `--help`.
557
-
558
- > **General rule:** a verb's most common action is its default. `mm-harness flows`
559
- > lists flows; `mm-harness flows promote` is the explicit subcommand. Never force
560
- > the user to type `list` when listing is what the verb does.
561
-
562
- The DISCOVER verbs are the agent's composition layer. The loop is:
563
- `actions --json` (vocabulary) → `call <action>` (try one action via real engine path) → `flows --json` (reusable flows) →
564
- compose `recipe.json` → `run --plan` (adapter-aware plan + validate, no device) → `run` (execute).
565
- `--json` is the **PRIMARY** form for every DISCOVER verb — the human form is the
566
- pretty-printed fallback. `manifest` is **RETIRED**: raw protocol dump rehomes to
567
- `actions --raw` (same JSON as `manifest --json` today); validation moves into
568
- `run --plan` / `doctor`. `validate` verb **DISSOLVED** into `run` (`run` validates first; `run --plan` = plan-only). **DISCOVER final shape: `actions` · `call` · `flows`.**
569
-
570
- ### `actions` (ROUTES-NOW → DISCOVER verb)
571
-
572
- **Synopsis:** `mm-harness actions [query] --adapter <p> [--json] [--categories | --category <name> | --action <name>]`
573
-
574
- **PRIMARY (agent):** search from the task vocabulary, for example `mm-harness actions positions --adapter mobile --json`, then request one full `--action` schema. Use `--categories` only when the task does not provide a useful search term.
575
-
576
- **`--json` output shape** (grounded — `actions --adapter core --json` confirmed):
577
- ```json
578
- {
579
- "adapter": "core",
580
- "actions": [
581
- {
582
- "name": "metamask.perps.read_positions",
583
- "kind": "custom",
584
- "category": "perps",
585
- "description": "Read live Perps positions...",
586
- "fields": ["account", "action", "market", "markets", "mode", "selector", "side", "symbol", "symbols", "timeout_ms"],
587
- "examples": [{ "node": { "action": "metamask.perps.read_positions", "account": "...", "symbol": "BTC" } }]
588
- }
589
- ]
590
- }
591
- ```
592
- `kind` is `"official"` (engine built-ins) or `"custom"` (MetaMask adapter actions). `category` uses manifest metadata when present, then stable generic groups for protocol primitives and durable action namespaces (`ui.*` → `ui`, `metamask.perps.*` → `perps`). `fields` lists every accepted parameter name. `examples[].node` is a copy-pasteable recipe node.
593
-
594
- **Human form:** `mm-harness actions --adapter mobile` — one line per action: `<name> (<kind>) <description> fields=<f1,f2,...>`.
595
-
596
- | Flag | Type | Default | ENV | Audience | Description |
597
- |---|---|---|---|---|---|
598
- | `--adapter <p>` | mobile\|extension\|core | auto-detect | `RECIPE_HARNESS_PLATFORM` | both | Target adapter · [DEFAULT-GAP] required today |
599
- | `--action <name>` | string | all | — | agent | Filter to one action; full schema + all examples |
600
- | `--categories` | bool | false | — | agent | Return only sorted category names and action counts |
601
- | `--category <name>` | string | all | — | agent | Return actions from one namespace-derived category |
602
- | `--action-manifest <path>` | path | bundled | — | agent | Override manifest |
603
- | `--raw` | bool | false | — | agent | Dump the underlying action manifest JSON (protocol version, registry version, all entries in raw registry format — same output as `manifest --json` today; replaces the retired `manifest` verb) |
604
- | `--json` | bool | false | — | **agent PRIMARY** | Full schema + fields + examples per action |
605
-
606
- The optional positional `query` searches names, categories, fields, and descriptions with typo tolerance. A single `--action` detail also returns up to five compact `relatedActions` names. Flow discovery remains separate.
607
- **Exit:** 0 / non-zero on engine error.
608
- **Maps-to:** A:`actions` (ROUTES-NOW); C/D:`actions` (ABSORB-LATER).
609
-
610
- ### `call <action>` (REAL as of wave 2 → DISCOVER verb; one-node recipe via real engine path)
611
-
612
- > **Implemented (wave 2):** `handleCall` in `runner/src/cli.ts` synthesizes a
613
- > single-node recipe and hands it to `runRecipe` — the SAME path `run` uses — so
614
- > `call` writes the same `summary.json` / `trace.json` / `artifact-manifest.json`
615
- > evidence. Fuzzy resolution, `--arg k=v` mapping, adapter-aware validation
616
- > (exit 5), and the `--json` shape are covered by `tests/contract/mm-harness-wave2.test.sh`.
617
-
618
- > Direct top-level actions (`mm-harness unlock`) are **REJECTED**: an open action
619
- > vocabulary would collide with the verb namespace; hot actions get deliberately
620
- > promoted to real verbs instead.
621
-
622
- **Synopsis:** `mm-harness call <action> [--arg k=v …] [--adapter <p>] [--json]`
623
-
624
- **What it does:** Execute ONE action from the adapter vocabulary as a single-node recipe through the **real engine path** — same trace/evidence shape as `run`, same phase state-machine, same `--json` contract. This is NOT a shortcut; it is one door into the same execution engine. The distinction: `call` = one node, `run` = a graph. Grounded in the hook layer's existing `run-action` concept (e.g., `run-action app.unlock`).
625
-
626
- **One execution path, two doors:** `call <action>` internally synthesizes a single-node recipe `{"nodes":[{"action":"<resolved>","args":{...}}]}` and hands it to the run engine. The result (`--json`) carries the same `trace[]`, `evidence`, `recovered[]`, `mutations[]`, and `schemaVersion` fields as `run --json`.
627
-
628
- **Fuzzy short-name resolution:** an unambiguous short name resolves to the full action name (`unlock` → `metamask.wallet.unlock`). If ambiguous, exit 2 and list candidates: `✗ "unlock" is ambiguous: metamask.wallet.unlock, metamask.session.unlock — use the full name`.
629
-
630
- **Human example:** `mm-harness call unlock` → resolves unambiguously to `metamask.wallet.unlock`
631
-
632
- **Agent example:** `mm-harness call metamask.perps.read_positions --arg symbol=BTC --arg side=long --adapter core --json`
633
-
634
- | Flag | Type | Default | ENV | Audience | Description |
635
- |---|---|---|---|---|---|
636
- | `<action>` positional | string | required | — | both | Full action name or unambiguous short name (fuzzy resolved from adapter vocabulary) |
637
- | `--arg <k=v>` | string (repeatable) | — | — | both | Action field value; parses JSON scalars/arrays/objects. Wrap a numeric-looking string as a JSON string, e.g. `--arg 'test_id="12345"'`. |
638
- | `--adapter <p>` | mobile\|extension\|core | auto-detect | `RECIPE_HARNESS_PLATFORM` | both | Target adapter |
639
- | `--json` | bool | false | — | **agent PRIMARY** | `{ action, resolvedAction, args, trace, evidence, recovered, mutations, exitCode, schemaVersion }` — same shape as `run --json` (one-node subset) |
640
-
641
- **Exit:** 0 success · 1 action failure · 2 bad args / ambiguous / unknown action · 5 validation failure (the one-node recipe failed adapter-aware validation).
642
- **Maps-to:** hook layer `run-action` (grounded); `handleCall` wired in `runner/src/cli.ts` (wave 2).
643
-
644
- ### `flows` / `flows promote` (ROUTES-NOW → DISCOVER verb)
645
-
646
- **What `flows` is:** the **reusable flow library** — pre-built, verified building blocks composed into recipes for efficiency and reuse. A recipe is mostly library-flow references plus a few unique steps; it is not rewritten from scratch each time. `flows promote` publishes a proven flow up a tier (personal → team → canonical), capturing it as a durable artifact. This is the composition story: discover what the library covers, compose from it, only author the delta.
647
-
648
- **Synopsis:** `mm-harness flows [--library <name=path>] [--json]` (bare = list; most common action is the default)
649
-
650
- **PRIMARY (agent):** `mm-harness flows --json`
651
-
652
- **`--json` output shape** (grounded — `flows-command.ts` `flowsListDocument()`):
653
- ```json
654
- {
655
- "sources": [
656
- { "name": "personal", "root": "/home/user/.farmslot/recipe-library" },
657
- { "name": "team", "root": "/path/to/project/recipe-library" }
658
- ],
659
- "flows": [
660
- {
661
- "ref": "perps/open-long",
662
- "source": "personal",
663
- "file": "perps/open-long.json",
664
- "description": "Open a long position on HL testnet",
665
- "requiredParams": ["symbol", "amount"],
666
- "shadows": ["team/perps/open-long"],
667
- "lastVerified": "2025-06-14T10:00:00Z"
668
- }
669
- ]
670
- }
671
- ```
672
- `source` is the library name whose copy wins (personal > domain > canonical). `shadows` lists lower-precedence entries that this flow overrides. `lastVerified` stamps from `flows promote --run <artifacts>`.
673
-
674
- **Human form:** `mm-harness flows` (bare = list, no subcommand needed) — text table: `<ref> source=<s> file=<f> [lastVerified=<t>] [shadows=<s,...>]`; description + `params:` line if present.
675
-
676
- | Flag | Type | Default | ENV | Audience | Description |
677
- |---|---|---|---|---|---|
678
- | `--library <name=path>` | string (repeatable) | personal/team then canonical | `RECIPE_LIBRARY_PATH` | agent | Library sources, precedence first |
679
- | `--json` | bool | false | — | **agent PRIMARY** | Full catalog with source, shadows, lastVerified |
680
-
681
- **[DISCOVERY-GAP]:**
682
- - No `--filter <keyword>` to search across refs/descriptions — cannot efficiently find flows covering a given screen or user story **[DISCOVERY-GAP]**
683
- - No `--platform <p>` filter — platform-specific flows not discoverable without scanning all refs **[DISCOVERY-GAP]**
684
- - No `--flow <ref>` to fetch a single flow's full body before composing — agent must read the file directly **[DISCOVERY-GAP]**
685
- - `shadows` is present in JSON but no human-readable "what would override what" summary **[DISCOVERY-GAP]**
686
- - Domain/coverage metadata absent — no way to ask "which flows cover the swap screen?" without scanning **[DISCOVERY-GAP]**
687
-
688
- **Exit:** 0 / 1 (no sources found) / non-zero on engine error.
689
- **Maps-to:** A:`flows` (ROUTES-NOW).
690
-
691
- ### `validate` — DISSOLVED into `run`
692
-
693
- **`mm-harness validate` is REMOVED as a standalone verb.** Zero capability lost.
694
-
695
- **Rehome:** `run` now validates first (adapter-aware: action existence, platform support, fixture preconditions) on every invocation. For plan-only validation without touching a device, use:
696
-
697
- ```
698
- mm-harness run recipe.json --plan [--json]
699
- ```
700
-
701
- `--plan` exits 0 if the plan is valid (exit 5 on errors). `--json` includes `plan[]` array + `validation` object with findings — same or richer than the former `validate --json` shape, plus adapter cross-check (the former [DISCOVERY-GAP] is gone: `run --plan --adapter <p>` validates action names against the specific adapter's manifest).
702
-
703
- **Implementation note:** `@farmslot/recipe-harness` `validate-command.ts` is called internally by the `run` phase state-machine (phase: `validate`). No external wiring of `handleValidate` is needed; the capability is subsumed. The old stub teaching message for `mm-harness validate` should be updated to point at `mm-harness run <recipe> --plan`.
704
-
705
- ## Runtime overlay: `install` · `verify` · `cleanup` (ROUTES-NOW → OVERLAY verbs)
706
-
707
- **Synopsis:** `mm-harness install|verify|cleanup [target] [flags]` — manage the
708
- per-checkout runtime overlay. Final home is top-level (no `harness` prefix). `live` is **REMOVED** — dissolved into `launch --verify` (same sequence: install overlay if missing → launch → CDP poll → smoke verify; zero capability lost — `launch --verify` never seeds fixtures).
709
-
710
- > **Auto-ensure note:** `launch` and `run` auto-install the overlay if
711
- > missing (with a one-line notice; `--heal=off` to disable). `install` is
712
- > the explicit form for CI, agents, and deterministic setup where the install step
713
- > must be auditable and separate. Human daily users need never call `install`
714
- > explicitly — daily verbs handle it.
715
-
716
- > **Self-healing note:** `verify` heals the overlay if missing (re-installs before probing health) and reports runtime state (Metro/Chrome/CDP) but does not restart it — use `launch` for that, or `doctor --fix` to repair without launching. `--json` gains `"recovered": [...]` when healing occurs.
717
-
718
- **Per-platform behavior:**
719
-
720
- | Verb | extension | mobile | core |
721
- |---|---|---|---|
722
- | `install` | `node inject.mjs` overlay → `temp/recipe/harness/extension/`; writes `.runner-source` | `inject.sh` overlay → `.../mobile/` | `inject.sh` overlay → `.../core/` |
723
- | `verify` | overlay/CDP/bridge health (prefers installed `scripts/verify.sh`) | Metro + JS bridge health | delegates to installed `mm-harness doctor --adapter core` |
724
- | `cleanup` | remove overlay (respects `.runner-source`) | remove overlay | remove overlay |
725
-
726
- **Options:**
727
-
728
- | Flag | Type | Default | ENV (agent) | Audience | Description |
729
- |---|---|---|---|---|---|
730
- | `[target]` positional | mobile\|extension\|core | auto-detected | — | human | Force adapter without a flag |
731
- | `--platform`/`--adapter <p>` | mobile\|extension\|core | = positional / auto (git remote / markers) | `RECIPE_HARNESS_PLATFORM` | agent | Explicit adapter; a later `--platform ios\|android` is forwarded to the script |
732
- | `--target <repo>` | path | `$PWD` | — | both | Checkout to operate on |
733
- | `--json` | bool | false | — | agent | One JSON summary on stdout (child output → stderr) |
734
- | `-- <args>` | passthrough | — | — | agent | Forwarded verbatim (e.g. `-- --cdp-port 6665`) |
735
-
736
- **Exit:** 0 pass · 1 orchestration error / missing delegate · 2 bad args / unsupported (`live` on core).
737
- **Failure (teaching):**
738
- ```
739
- ✗ verify core failed
740
- core recipe harness not installed: missing delegate <path>.
741
- Next: mm-harness install --platform core --target <repo>
742
- ```
743
- **JSON:** `{ schemaVersion, command:"harness", action, adapter, target, autoDetected, status, exitCode }`.
744
- **Maps-to:** A:`harness <verb>` (ROUTES-NOW); E: skill (REMOVE); F: orchestration entry points (KEEP-INTERNAL); C/D:`prepare`,`ready\|ensure-ready` (ABSORB-LATER).
745
-
746
- **Core-runner install — the public surface for farm packs.** `mm-harness install
747
- --adapter core --target <repo>` is the pack-facing way to install the headless
748
- core runner into a slot: it fronts `adapters/core/inject.sh` (the same leaf a farm
749
- pack used to clone-and-run itself) and produces the runner delegate at
750
- `<harness>/core/runner/bin/mm-harness`. Because the inject leaf ships inside the
751
- published package, packs no longer clone a runner checkout — they call this command
752
- and inherit `RECIPE_HARNESS_ROOT` / `FARMSLOT_ROOT` through the environment. There
753
- is no separate top-level `install-core` command; `--adapter core` on the existing
754
- `install` is the surface (grounded: `tests/contract/core-install-cli.test.sh`).
755
-
756
- ## Advanced (collapsed → KEEP-INTERNAL)
757
-
758
- **All six ADVANCED verbs are removed from the CLI surface and `--help`** — their logic
759
- moves inside the self-healing daily verbs and internal plumbing. No user-facing verb
760
- remains in this group.
761
-
762
- | Verb | Fate | Rehomed inside |
763
- |---|---|---|
764
- | `runtime-health` | KEEP-INTERNAL | `launch`/`verify` self-healing — extension CDP health probe |
765
- | `runtime-decision` | KEEP-INTERNAL | `launch` self-healing — quick vs full build decision; mobile bridge health |
766
- | `runtime-launch` | KEEP-INTERNAL | `launch` self-healing — extension runtime launch sequence (snapshot dist → Chrome → CDP poll; never touches the wallet fixture) |
767
- | `resolve-extension` | KEEP-INTERNAL | `launch`/`launch --verify` internal plumbing — extension id resolution at attach time |
768
- | `ensure-ready` | KEEP-INTERNAL | `launch`/`verify` self-healing — stale tab cleanup + CDP readiness wait |
769
- | `self-test` | KEEP-INTERNAL | CI smoke test; callable via `package.json` scripts (`yarn self-test`); not in `--help` |
770
-
771
- `manifest` is **REMOVED** (see Surface A fate mapping): (1) validation → `doctor` readiness check; (2) raw dump → `actions --raw`; (3) path + protocol version → `doctor` explain-my-setup. The raw JSON shape previously returned by `manifest --json` is now `mm-harness actions --raw`.
772
-
773
- Porcelain infra (`ports`, `runtime-status`, `decision`, `status`, `stop`) folds into
774
- `mm-harness` status/`--json` flags (ABSORB-LATER).
775
-
776
- ## Implementation notes (spec-only; draft code stays hand-rolled)
777
-
778
- - **CLI library:** the refactor implementation adopts **`commander`** — the same
779
- library `@farmslot/recipe-harness` uses for its CLI (confirmed:
780
- `packages/recipe-harness/package.json` deps `"commander": "^12.0.0"`, bin
781
- `bin/farmslot-recipe.mjs`). Use it for grouped help, positionals, subcommands,
782
- and completion generation, replacing the hand-rolled parser in `runner/src/cli.ts`.
783
- - **Positional grammar:** `mm-harness <verb> [target] [flags]`; target is the
784
- human form, `--platform` the agent form, wired via commander positionals.
785
- - **Completions (dynamic, sourced from DISCOVER layer — SHIPPED):** `mm-harness`
786
- ships zsh + bash completions in the npm package (`scripts/completions.sh`) with an
787
- install command (`mm-harness completions install` → `scripts/install-completions.sh`,
788
- prints the `source` line to add to `~/.zshrc` / `~/.bashrc`; the farm installer
789
- companion can auto-source it later — precedent: farmslot's `compdef` flow).
790
- Completions are **dynamic**, not static:
791
- - **Commands + targets + flags:** static (from the mm-harness surface): commands,
792
- the `launch` positional (`ios|android`), `fixtures` (`sync|set`), and per-command flags.
793
- - **Action names for `call`:** completed live from
794
- `mm-harness completion-candidates actions` (adapter manifest, cache-backed).
795
- - **Flow refs for `run`:** completed live from `mm-harness completion-candidates flows`
796
- (recipe library) alongside recipe-file completion. **REAL** on
797
- `@farmslot/recipe-harness >= 0.3.3` — `completion-candidates flows` enumerates the
798
- resolved library flows (`loadRecipeLibraries`) instead of degrading to empty.
799
- - **Cache:** per-checkout cache file (`{runtimeDir}/.completion-cache.json`),
800
- invalidated on `mm-harness install` (overlay change). TTL: short (60 s fallback),
801
- refreshed lazily on first tab-complete after invalidation. Cache miss = silent
802
- static fallback; never blocks the shell.
803
- - **Principle:** DISCOVER feeds the agent (compose), the shell (complete), and the
804
- docs (cheatsheet) — one source of truth for what actions and flows exist.
805
- Replaces `completions/_recipe` (removed).
806
- - **Resolution order everywhere:** flag > env > config/fixture > built-in default.
807
-
808
- ---
809
-
810
- # Part 3 — Caller audit (0 unmapped)
811
-
812
- Every place that invokes a removed name today → what it calls after the wave.
813
- An unlisted caller = the wave can't ship. Grounded in `grep` over this repo +
814
- the sibling `farmslot` repo.
815
-
816
- ## In-repo (metamask-recipe-runner)
817
-
818
- | Caller (path) | Invokes today | After |
819
- |---|---|---|
820
- | `orchestration/{core,mobile}/inject.sh`, `orchestration/extension/inject.mjs` | Writes delegate `runner/bin/metamask-recipe` into the installed overlay | Write `runner/bin/mm-harness` delegate |
821
- | `orchestration/mobile/{launch,live}.sh`, `orchestration/extension/{live,launch,ensure-browser}.sh` | `RUNNER_BIN=…/runner/bin/metamask-recipe`; human hints "Run metamask-recipe … prepare / runtime-launch" | `mm-harness` bin path + updated hint text |
822
- | `orchestration/extension/runtime.ts` (`runtime-launch` help block) | Prints `bin/metamask-recipe run … --adapter extension …` | `mm-harness run …` |
823
- | `orchestration/extension/live.sh` (`display_args`, `easyCommand`) | resolved — prints `mm-harness launch --verify …` | done |
824
- | `orchestration/mobile/live.sh` (`easyCommand`, echo) | resolved — prints `mm-harness launch <target> --verify` | done |
825
- | `orchestration/{core,mobile,extension,lib}/*` error prefixes `metamask-recipe: …` | cosmetic name in messages | `mm-harness: …` |
826
- | `completions/_recipe` | zsh completion for `recipe`/`mm-recipe`/`mme-recipe` | Removed; replaced by bundled `mm-harness` completions |
827
- | `bin/metamask-recipe` `mobile`/`extension` passthrough | execs `bin/mm-recipe` / `bin/mme-recipe` | Absorbed — `mm-harness` verbs; passthrough removed |
828
- | `package.json` `bin`, `scripts` (`self-test`, `manifest:*`) | `bin/metamask-recipe …` | `bin/mm-harness …`; `manifest:*` scripts → `mm-harness actions --raw` (protocol dump) or removed if CI-only |
829
-
830
- ## Sibling repo (farmslot) — updated in the same wave
831
-
832
- | Caller (path) | Invokes today | After |
833
- |---|---|---|
834
- | `projects/metamask-extension-farm/project.json`, `projects/metamask-core-farm/project.json` | farm hooks / commands calling the runner bin + skill | `mm-harness` verbs |
835
- | `projects/metamask-extension-farm/setup/*.sh` (`launch-browser.sh`, `health-check.sh`, `ensure-runtime-ready.sh`, `preflight.sh`) | `metamask-recipe` / `mme-recipe` invocations | `mm-harness` verbs |
836
- | `projects/metamask-extension-farm/setup/generate-fixture.cjs` | fixture generation referencing the runner | `mm-harness fixtures …` where applicable |
837
- | `projects/metamask-core-farm/scripts/check-core-farm.mjs`, `fixtures/README.md`, `AGENTS.md` | `metamask-recipe` verbs | `mm-harness` verbs |
838
- | `projects/*/templates/worker/*.md`, `fixtures/**/*.md`, `learnings/LEARNINGS.md`, architecture docs | documented `metamask-recipe`/`mm-recipe`/`mme-recipe` commands | rewritten to `mm-harness` |
839
- | farm project-shortcut hooks (the slot-bound "same verb name" bindings) | wrap runner/skill invocations | wrap `mm-harness` (one verb vocabulary, three bindings) |
840
-
841
- **Completeness:** every grep hit above is mapped; **0 unmapped**. Any new caller
842
- discovered before the wave must be added here first.
843
-
844
- ---
845
-
846
- # Part 4 — Env-var audit (0 unmapped)
847
-
848
- Env vars a human is told to type today become flags with config/fixture defaults.
849
- `[ENV-GAP]` = a human types it today; end state = flag + stored default, zero
850
- prefix. Agent/CI may still set the env (resolution: flag > env > config > default).
851
-
852
- | Env var (today) | Human-typed? | End-state flag | Config/fixture default | Tag |
853
- |---|---|---|---|---|
854
- | `MOBILE_PREFLIGHT_MODE` / `RECIPE_HARNESS_MOBILE_PREFLIGHT_MODE` | yes | `launch --build` (tier) | quick tier default | [ENV-GAP] |
855
- | `MM_PASSWORD` (unlock) | yes | none — `fixtures set` reads the fixture | wallet fixture password | [ENV-GAP] — the canonical anti-pattern |
856
- | `RECIPE_WALLET_FIXTURE` | yes | `fixtures set --fixture` | slot `wallet-fixture.json` | [ENV-GAP] |
857
- | `RECIPE_LIBRARY_PATH` | yes | `run/flows --library` | personal/team then canonical | [ENV-GAP] |
858
- | `IOS_SIMULATOR` / `ADB_SERIAL` / `ANDROID_SERIAL` / `ANDROID_DEVICE` | yes | `launch --device` | first booted sim / slot config | [ENV-GAP] |
859
- | `CDP_PORT` / `RECIPE_CDP_PORT` | yes | `--cdp-port` | slot/runtime context | [ENV-GAP] (agent flag ok) |
860
- | `WATCHER_PORT` / `METRO_PORT` / `RECIPE_WATCHER_PORT` | yes | `--watcher-port` | slot/runtime context | [ENV-GAP] |
861
- | `RECIPE_LOG_UI` / `RECIPE_LOG_EVENTS` | yes | `logs --full` / `--events` | compact, 10 | [ENV-GAP] |
862
- | `RECIPE_HARNESS_ROOT` / `RECIPE_RUNTIME_DIR` | rarely | `--harness-root` (agent) | `path-defaults.json` | KEEP-INTERNAL |
863
- | `FARMSLOT_SLOT_ID` / `RECIPE_SLOT_ID` / `SLOT_ID` | fleet only | `--slot` | dispatch context | KEEP-INTERNAL (agent/fleet) |
864
- | `PLATFORM` / `RECIPE_HARNESS_PLATFORM` | agent | `--platform` / positional target | auto-detect | KEEP-INTERNAL (agent) |
865
- | `MOBILE_METRO_CLEAR` / `MOBILE_BUNDLE_PREWARM*` / `METRO_MAX_WORKERS` / `RECIPE_UP_INSTALL_ATTEMPTED` / `METAMASK_RECIPE_*` | internal | internal knobs of `launch` | built-in | KEEP-INTERNAL |
866
-
867
- **Completeness:** every human-typed env prefix in the current surface is mapped to
868
- a flag + default (`[ENV-GAP]`); internal/agent-only vars are KEEP-INTERNAL;
869
- **0 unmapped**.
870
-
871
- **Implemented:** the `[ENV-GAP]` flags exist with resolution order **flag > env >
872
- config/default** (the flag sets the env var the porcelain reads, so it wins; an
873
- absent flag leaves the agent/CI env untouched):
874
- - `launch --device` (`IOS_SIMULATOR` for ios · `ADB_SERIAL`/`ANDROID_SERIAL`/`ANDROID_DEVICE` for android); `provision --device` is an alias for `--simulator`
875
- - `launch --cdp-port` (`CDP_PORT`/`RECIPE_CDP_PORT`) · `launch --watcher-port` (`WATCHER_PORT`/`METRO_PORT`/`RECIPE_WATCHER_PORT`) — numeric, teaching error otherwise
876
- - `launch --build` covers `MOBILE_PREFLIGHT_MODE` (tier) · `provision --slot --runtime-dir --watcher-port` accepts farm slot context · `logs --full`/`--events` cover `RECIPE_LOG_UI`/`RECIPE_LOG_EVENTS`
877
- - `fixtures set --fixture` (`RECIPE_WALLET_FIXTURE`) · `run`/`flows --library` (`RECIPE_LIBRARY_PATH`)
878
- - `MM_PASSWORD` stays flag-less by design — `fixtures set` reads the password FROM the fixture.
879
-
880
- Precedence is contract-tested in `tests/contract/mm-harness-wave5.test.sh`.
881
-
882
- ---
883
-
884
- # Part 5 — Agent Error Contract
885
-
886
- Agents (orchestrators, CI, LLM tool-callers) need machine-grade signals to decide: retry, escalate, or abort. This section defines the complete contract: structured errors, stable codes, exit taxonomy, event stream, and healing policy. All `--json` output from every verb conforms to this contract.
887
-
888
- ## §5.1 Structured error object
889
-
890
- Every failure in `--json` mode produces an `error` field at the top level:
891
-
892
- ```json
893
- {
894
- "error": {
895
- "code": "METRO_START_FAILED",
896
- "message": "Metro bundler failed to start within 60s",
897
- "cause": "port 8081 already in use by pid 12345",
898
- "retryable": true,
899
- "userAction": "Free port 8081 (kill pid 12345), then re-run: mm-harness launch ios"
900
- }
901
- }
902
- ```
903
-
904
- | Field | Type | Description |
905
- |---|---|---|
906
- | `code` | string (UPPER_SNAKE) | Stable machine code — never changes; safe to `switch` on in agent code |
907
- | `message` | string | Human-readable summary |
908
- | `cause` | string \| null | Root cause detail (OS error, process output, etc.) |
909
- | `retryable` | boolean | `true` = same command may succeed on retry; `false` = agent must change inputs or escalate |
910
- | `userAction` | string \| null | Exact next command or manual step |
911
-
912
- Successful lifecycle transitions may add a top-level `next` command when exactly
913
- one follow-up is correct (for example install → verify or stop → relaunch).
914
- Discovery, read-only, and terminal evidence commands intentionally omit it rather
915
- than inventing guidance. Agents must therefore treat `next` as optional.
916
-
917
- Public command-grammar failures happen before runtime dispatch and always exit 2.
918
- Their machine envelope is deliberately smaller and stable: agents branch on
919
- `error.code`, never on prose.
920
-
921
- ```json
922
- {
923
- "schemaVersion": 1,
924
- "command": "actions",
925
- "status": "fail",
926
- "error": {
927
- "code": "CLI_UNKNOWN_OPTION",
928
- "message": "unknown option '--categoriez'. Valid options for mm-harness actions: ...",
929
- "userAction": "Did you mean '--categories' instead of '--categoriez'? Try: mm-harness actions --adapter mobile"
930
- },
931
- "exitCode": 2
932
- }
933
- ```
934
-
935
- Stable grammar codes are `CLI_UNKNOWN_COMMAND`, `CLI_UNKNOWN_OPTION`,
936
- `CLI_MISSING_OPTION_VALUE`, `CLI_INVALID_OPTION_VALUE`,
937
- `CLI_MISSING_POSITIONAL`, `CLI_INVALID_POSITIONAL`,
938
- `CLI_EXCESS_POSITIONAL`, and `CLI_UNEXPECTED_PASSTHROUGH`. Every public
939
- command is tracked in `docs/CLI-ERGONOMICS-AUDIT.md`; private adapter leaves keep
940
- their own grammar behind explicit `--` passthrough.
941
-
942
- ## §5.2 Stable recovery codes (`recovered[]`)
943
-
944
- `recovered[]` in `--json` output lists what was healed. These are stable enum values — not prose, safe to match in agent code:
945
-
946
- | Code | What happened |
947
- |---|---|
948
- | `overlay.reinstalled` | Runtime overlay was missing or corrupted; reinstalled inline |
949
- | `metro.restarted` | Metro bundler was dead; restarted and bundle prewarmed |
950
- | `chrome.reopened` | Chrome was closed or crashed; reopened with correct profile/flags |
951
- | `cdp.reconnected` | CDP websocket was stale/disconnected; reconnected and verified |
952
- | `tabs.cleared` | Stale home/onboarding tabs were open; closed before proceeding |
953
-
954
- All recovery codes are **infra-only**. Healing never touches wallet state: a missing/invalid fixture is not "recovered" — the verb stops with `recoverable:false` and `userAction: "run mm-harness fixtures set"` (the only path that mutates wallet data).
955
-
956
- ## §5.3 `mutations[]` — side-effect manifest
957
-
958
- Every `--json` response includes `mutations[]` listing all side effects the command produced on the system. Agent can diff pre/post state or verify cleanup:
959
-
960
- ```json
961
- {
962
- "mutations": [
963
- { "type": "file", "path": "/path/to/temp/recipe/harness/extension/inject.js", "action": "created" },
964
- { "type": "process", "name": "metro", "pid": 45678, "action": "started" },
965
- { "type": "port", "port": 8081, "action": "bound" },
966
- { "type": "fixture", "path": "/path/to/wallet-fixture.json", "action": "seeded" },
967
- { "type": "tab", "url": "chrome-extension://abc/home.html", "action": "closed" }
968
- ]
969
- }
970
- ```
971
-
972
- | `type` | `action` values | When emitted |
973
- |---|---|---|
974
- | `file` | `created` / `deleted` / `modified` | Overlay install/cleanup; fixture write |
975
- | `process` | `started` / `killed` | Metro start/stop; Chrome open/close |
976
- | `port` | `bound` / `freed` | Metro/CDP port lifecycle |
977
- | `fixture` | `seeded` / `cleared` | Wallet fixture apply/reset |
978
- | `tab` | `opened` / `closed` | Chrome tab lifecycle (extension CDP) |
979
-
980
- ## §5.4 Phase state-machine
981
-
982
- Summary `--json` responses retain their command-specific `phase` field. `--json-stream` emits transitions as they occur, so an agent can distinguish active work from a stalled command.
983
-
984
- **`run` phases:**
985
- ```
986
- resolve → validate → install → healthcheck → execute
987
- ```
988
-
989
- **`launch` phases:**
990
- ```
991
- resolve → install → healthcheck → launch → verify
992
- ```
993
-
994
- `recover` is conditional and appears immediately before a bounded recovery attempt; `verify` appears only for `launch --verify`.
995
-
996
- | Phase | What happens |
997
- |---|---|
998
- | `resolve` | Recipe/target/adapter resolved; library sources loaded |
999
- | `install` | Overlay auto-ensure check; install if missing (overlay install notice to stderr on first install) |
1000
- | `healthcheck` | Probe runtime state (Metro/CDP/bridge) |
1001
- | `recover` | Apply `--heal` policy; execute recovery actions; emit `recovered[]` |
1002
- | `validate` | Adapter-aware recipe validation (action existence, platform support, fixture preconditions). `run --plan` exits here. |
1003
- | `launch` | Start/attach to app (Metro prewarm, Chrome open, CDP attach) |
1004
- | `execute` | Run recipe nodes; collect trace/evidence (`run` only) |
1005
- | `verify` | Smoke verify after launch (`launch --verify` only) |
1006
-
1007
- ## §5.5 JSONL event stream (`--json-stream`)
1008
-
1009
- `run` and `launch` accept `--json-stream`. Each progress event is written as one complete JSON object plus a newline, so a piped reader can parse it before the command finishes. Schema is versioned via `schemaVersion`; every event also carries `command` and `ts`.
1010
-
1011
- Stream mode owns stdout: existing human or subprocess output is routed to stderr. If both `--json` and `--json-stream` are supplied, stream mode wins. The existing summary `--json` bytes are unchanged when stream mode is absent.
1012
-
1013
- ```jsonl
1014
- {"schemaVersion":1,"command":"run","event":"phase","phase":"resolve","ts":"2026-07-02T10:00:00.000Z"}
1015
- {"schemaVersion":1,"command":"run","event":"phase","phase":"validate","ts":"2026-07-02T10:00:00.100Z"}
1016
- {"schemaVersion":1,"command":"run","event":"phase","phase":"execute","ts":"..."}
1017
- {"schemaVersion":1,"command":"run","event":"node","nodeId":"unlock","action":"metamask.wallet.unlock","status":"running","ts":"..."}
1018
- {"schemaVersion":1,"command":"run","event":"node","nodeId":"unlock","action":"metamask.wallet.unlock","status":"passed","ts":"..."}
1019
- {"schemaVersion":1,"command":"run","event":"complete","status":"pass","exitCode":0,"reportPath":"/.../report.md","artifactManifestPath":"/.../artifact-manifest.json","recovered":[],"mutations":[],"ts":"..."}
1020
- ```
1021
-
1022
- Event types: `phase` · `node` · `mutation` · `recovery` · `error` · `complete`. Node events use stable recipe `nodeId` values rather than an inferred numeric order. Exactly one terminal `complete` event is last on normal success or handled failure, giving agents a clean sentinel and compact evidence paths without repeating the full run result.
1023
-
1024
- ## §5.6 Exit code taxonomy
1025
-
1026
- Consistent across all verbs. Agents must not retry on exit 2 or 4.
1027
-
1028
- | Code | Meaning | Agent action |
1029
- |---|---|---|
1030
- | 0 | Success | Proceed |
1031
- | 1 | Recipe/operation failed (app-logic: assertion, action error, fixture mismatch) | Diagnose; do NOT auto-retry blindly |
1032
- | 2 | Bad args / unsupported platform / missing required input | Fix command; do NOT retry |
1033
- | 3 | Infra failure not auto-healed (healing `off`, or a recovery bound was hit) | Re-run with default healing; escalate if still 3 |
1034
- | 4 | Bounded / not-retryable (same recovery already failed once; failure needs a wallet fixture write — run `fixtures set`; recipe running) | Escalate; do NOT retry |
1035
- | 5 | Validation failure (`run`, `run --plan`, `call`): adapter-aware validation found errors | Fix recipe; do NOT retry |
1036
-
1037
- **Bound-specific `error.code` values.** When a recovery bound fires, `error.code` names the exact bound (not a generic `LAUNCH_FAILED`) so agents branch on the cause. Every bound also carries `error.originalError` — the original failure output verbatim — so the real cause is never replaced by the classification note. Shared by `launch`, `run`, and `call`:
1038
-
1039
- | `error.code` | Fires when | Exit |
1040
- |---|---|---|
1041
- | `APP_LOGIC_FAILURE` | Failure is app-logic (assertion, wrong route) — healing cannot help; surfaced verbatim | 1 |
1042
- | `WALLET_STATE_REQUIRED` | Recovery would need a seeded wallet — healing never touches fixtures (`userAction: "run mm-harness fixtures set"`) | 4 |
1043
- | `RECIPE_RUNNING` | A recipe is executing — recovery refused to avoid corrupting mid-run state | 4 |
1044
- | `SAME_RECOVERY_TWICE` | The same recovery already failed once this invocation — refused to avoid a loop | 4 |
1045
-
1046
- **Stopping agent retry loops:** when `recoverable:false` appears in `--json` alongside `attemptedRecoveries[]`, the agent MUST NOT retry. The harness already attempted recovery and it failed or hit a bound.
1047
-
1048
- ```json
1049
- {
1050
- "recoverable": false,
1051
- "attemptedRecoveries": ["metro.restarted"],
1052
- "error": { "code": "SAME_RECOVERY_TWICE", "retryable": false, "originalError": "...", ... }
1053
- }
1054
- ```
1055
-
1056
- ## §5.7 Healing policy (`--heal`)
1057
-
1058
- Controls what the harness is allowed to repair autonomously. Defaults differ by verb: `launch` defaults `auto`; `run` and `call` default `infra-only`.
1059
-
1060
- | Value | Behavior |
1061
- |---|---|
1062
- | `off` | No healing, no auto-install. Fail fast with a teaching error; exact broken state preserved (repro-preserving). |
1063
- | `infra-only` | Heal overlay/runtime transport (Metro, Chrome, CDP, tabs) only. NEVER touches wallet state. `run`/`call` default. If a wallet write is needed → `recoverable:false` exit 4, `userAction: "run mm-harness fixtures set"`. |
1064
- | `auto` | Auto-ensure the overlay + heal transport silently. Still NEVER touches wallet state (that is `fixtures set`, explicit only). `launch` default. |
1065
-
1066
- `--yes` confirms any prompt non-interactively (CI/agent safe). The three `--heal` values above are the ONLY opt-out mechanism: healing bounds are ALWAYS enforced and no flag bypasses them, and wallet state is mutated only by the explicit `fixtures set`.
1067
-
1068
- **Recovery bounds (always enforced regardless of `--heal`):**
1069
- - Recipe currently executing → refuse all recovery (would corrupt mid-run state)
1070
- - Failure is app-logic (assertion failure, wrong route) → refuse infra recovery (wrong tool)
1071
- - Same recovery already failed once in this invocation → refuse (avoid infinite loops)
1072
-
1073
- ---
1074
-
1075
- ## Considered and rejected
1076
-
1077
- **Teaching-tombstone stub bins (`mm-harness-legacy`, `metamask-recipe-stub`, etc.):** Rejected. Installing stub binaries that print "this command moved to mm-harness" before any user has adopted the new surface creates pre-adoption debt with zero payoff. The coordinated wave (Part 3) updates all callers atomically. No tombstone bins needed; no user is mid-flight on the old surface at wave time.
1078
-
1079
- ---
1080
-
1081
- # Part 6 — How farmslot calls mm-harness (preflight & the fleet binding)
1082
-
1083
- "Preflight" survives as a **capability, not an exposed concept**: it is what `launch`
1084
- does before returning (readiness: Metro/bundle → app → health/bridge poll), with
1085
- `launch --verify` as the prove-it tier and `doctor` as the read-only view.
1086
- `FARMSLOT_MOBILE_PREFLIGHT_MODE` is an `[ENV-GAP]` row that dies (Part 4).
1087
-
1088
- The fleet is just another agent-grade caller:
1089
-
1090
- - farmslot prepare profiles keep their phases (git / fixtures / deps = farm-level
1091
- checkout management, NOT harness work); the preflight/health hooks become one
1092
- call: `mm-harness launch <target> --verify --heal=auto --json`.
1093
- - The farm's `preflight.sh` / `runway-preflight.sh` step-2 logic thins to that call
1094
- (already Part-3 caller-audit rows; updated in the caller sweep wave). The Runway
1095
- artifact install remains a farm vehicle invoked before the harness call.
1096
- - **Same-capability guarantee is by construction:** the machine contract (`--json`
1097
- schemas, `--heal`, exit-code taxonomy, `recovered[]` / `mutations[]` /
1098
- `recoverable:false`) is the same one agents use — gateway slot-tracking /
1099
- gate-summary consume structured output instead of scraping bash logs, strictly
1100
- richer than today.
1101
- - **Three bindings, one verb:** the engineer types `mm-harness launch ios`; the
1102
- slot's prepare hook calls the same verb with slot context; dispatch scales it
1103
- across the fleet.
1104
-
1105
- ## Appendix — Extension runtime lifecycle (internal notes)
1106
-
1107
- Internal implementation notes for the Extension runtime lifecycle — the
1108
- self-healing path that gets a checkout from "installed" to "a live, verified
1109
- extension under CDP control". These are **not** a public command surface: the
1110
- probes below are internal functions, composed for you by `launch --verify` and by
1111
- the readiness commands (`doctor --fix`, `verify`). The runner is the source of
1112
- truth for Extension harness injection, readiness decisions, extension-id
1113
- resolution, and live CDP health checks.
1114
-
1115
- Public entry points:
1116
-
1117
- - **`install`** writes the Extension runtime overlay under the configured harness
1118
- root. CI/agents run it explicitly; the daily-loop commands auto-ensure it.
1119
- - **`verify`** confirms the overlay is present and healthy — a live CDP health
1120
- probe when a runtime is reachable, no launch.
1121
- - **`launch --verify`** installs the overlay if missing, launches/attaches the
1122
- extension, and polls CDP until the runtime is ready.
1123
- - **`doctor` / `doctor --fix`** report readiness and (with `--fix`) run the
1124
- healing steps below WITHOUT starting the app.
1125
-
1126
- Internal lifecycle stages, composed in order (each idempotent, each reporting the
1127
- cheapest next action so the path converges rather than rebuilding from scratch):
1128
-
1129
- - **Extension-id resolution** — resolves the deterministic unpacked extension id
1130
- from `dist/chrome/manifest.json`, optionally verified against a live CDP target.
1131
- Runs first; every later stage keys off this id.
1132
- - **Readiness decision** — inspects the checkout + live browser and returns the
1133
- cheapest next action (`install`, `build`, `relaunch`, or `ready`) plus the
1134
- concrete `actions[]` to execute.
1135
- - **Runtime launch** — launches Chrome with the installed harness helper, seeds
1136
- the wallet fixture, and runs live smoke verification. Two tiers: reuse the
1137
- existing dist (quick relaunch), or the clean-build path (clear webpack cache,
1138
- start the harness-owned watcher, wait for a clean compile, then launch + verify).
1139
- - **Convergence** — converges the live browser to exactly one healthy `home.html`
1140
- tab (closing strays, reopening if needed) and confirms it with the health probe.
1141
- - **Health probe** — read-only liveness probe for the running extension over CDP;
1142
- the final gate of convergence and the standalone answer for `verify`.