@deeeed/metamask-harness 0.16.0 → 0.17.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (119) hide show
  1. package/CHANGELOG.md +44 -0
  2. package/README.md +90 -137
  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/dist/adapters/extension/console-capture.js +84 -0
  61. package/dist/adapters/extension/runtime-decision.js +14 -2
  62. package/dist/adapters/extension/surface.js +3 -2
  63. package/dist/adapters/mobile/prepare.js +22 -7
  64. package/dist/adapters/mobile/surface.js +1 -1
  65. package/dist/adapters/resolve-slot-ports.js +0 -2
  66. package/dist/adapters/slot-ports.js +8 -0
  67. package/dist/cli.js +5 -11
  68. package/dist/command-contract.js +441 -0
  69. package/dist/command-journal.js +225 -0
  70. package/dist/commands/call.js +43 -20
  71. package/dist/commands/check.js +9 -3
  72. package/dist/commands/debug.js +38 -31
  73. package/dist/commands/device-target.js +27 -12
  74. package/dist/commands/doctor.js +19 -6
  75. package/dist/commands/fixtures.js +19 -11
  76. package/dist/commands/last.js +52 -0
  77. package/dist/commands/launch/index.js +166 -59
  78. package/dist/commands/logs.js +8 -6
  79. package/dist/commands/manifest.js +114 -29
  80. package/dist/commands/parse-args.js +1 -0
  81. package/dist/commands/provision.js +10 -3
  82. package/dist/commands/run-engine.js +27 -42
  83. package/dist/commands/run-report.js +1 -1
  84. package/dist/commands/run.js +243 -41
  85. package/dist/commands/shared.js +11 -1
  86. package/dist/commands/status-probe.js +1 -1
  87. package/dist/commands/status.js +1 -1
  88. package/dist/commands/stop.js +7 -2
  89. package/dist/doctor.js +1 -4
  90. package/dist/harness.js +19 -125
  91. package/dist/json-stream.js +57 -0
  92. package/dist/live-adapter-contract.js +6 -20
  93. package/dist/mm-harness-cli.js +111 -41
  94. package/dist/paths.js +2 -5
  95. package/dist/run-diagnostics.js +35 -40
  96. package/dist/runner.js +32 -1
  97. package/dist/runtime-context.js +26 -8
  98. package/docs/CHEATSHEET.md +39 -52
  99. package/docs/CLI-ERGONOMICS-AUDIT.md +32 -0
  100. package/docs/CLI-ERGONOMICS-HUMAN-QA.md +48 -0
  101. package/docs/CODE-MAP.md +1 -1
  102. package/docs/UX-PRINCIPLES.md +2 -0
  103. package/docs/VIDEO-DEMO-VALIDATION.md +62 -0
  104. package/docs/architecture.md +65 -375
  105. package/docs/live-adapter-contract.md +3 -1
  106. package/library/actions/core/perps/_controller.mjs +10 -55
  107. package/library/manifests/mobile.action-manifest.json +1 -1
  108. package/package.json +3 -2
  109. package/scripts/completions.sh +0 -0
  110. package/scripts/install-completions.sh +0 -0
  111. package/adapters/mobile/overlay/app/dev-tools/AgenticService/AgentStepHud.tsx.patch +0 -185
  112. package/adapters/mobile/overlay/app/dev-tools/AgenticService/AgenticService.ts.patch +0 -1662
  113. package/adapters/mobile/overlay/compat/README.md +0 -28
  114. package/adapters/mobile/overlay/compat/rn81-message-event-source.patch +0 -42
  115. package/adapters/shared/resolve-farmslot-ports-core.mjs +0 -3
  116. package/adapters/shared/resolve-farmslot-ports.mjs +0 -5
  117. package/adapters/shared/resolve-farmslot-ports.sh +0 -7
  118. package/docs/CLI-SPEC.md +0 -1102
  119. package/docs/MENTAL-MODEL.md +0 -295
@@ -1,26 +1,54 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
3
  import { createHash } from "node:crypto";
4
- import { spawn, spawnSync } from "node:child_process";
4
+ import { spawnSync } from "node:child_process";
5
5
  import { getAdapterSurface } from "./adapters/surface.js";
6
6
  import { runnerDir } from "./paths.js";
7
+ import { ensureExtensionConsoleCapture } from "./adapters/extension/console-capture.js";
7
8
  const MAX_CAPTURE_BYTES = 512 * 1024;
8
9
  const MAX_FINDINGS = 20;
9
10
  const MAX_PREVIEW_CHARS = 320;
11
+ function readRunDiagnosticsDocument(diagnosticsPath) {
12
+ if (typeof diagnosticsPath !== "string" || diagnosticsPath.length === 0) return null;
13
+ try {
14
+ const document = JSON.parse(fs.readFileSync(diagnosticsPath, "utf8"));
15
+ return isRunDiagnosticsDocument(document) ? document : null;
16
+ } catch {
17
+ return null;
18
+ }
19
+ }
20
+ function formatRunDiagnosticsForHuman(diagnostics, adapter) {
21
+ if (!diagnostics) {
22
+ return [adapter === "core" ? "N/A \u2014 Core is headless and has no application log source." : "UNAVAILABLE \u2014 application diagnostics could not be collected for this run."];
23
+ }
24
+ const status = diagnostics.status === "clean" ? "CLEAN" : diagnostics.status === "review" ? "REVIEW" : "UNAVAILABLE";
25
+ const lines = [`${status} \u2014 ${diagnostics.note}`];
26
+ for (const finding of diagnostics.findings) {
27
+ const count = finding.count > 1 ? ` \xD7${finding.count}` : "";
28
+ lines.push(`${finding.level.toUpperCase()}${count} \u2014 ${finding.preview}`);
29
+ }
30
+ return lines;
31
+ }
10
32
  async function beginRunDiagnostics(adapter, projectRoot) {
11
33
  const source = getAdapterSurface(adapter).appLogSource(projectRoot);
12
34
  if (!source) return null;
13
35
  const mobileIssueBuffer = adapter === "mobile" && armMobileIssueBuffer(projectRoot) ? { projectRoot } : void 0;
14
- const extensionConsoleCapture = adapter === "extension" ? await startExtensionConsoleCapture(source, projectRoot) : void 0;
36
+ if (adapter === "extension") {
37
+ await ensureExtensionConsoleCapture(projectRoot).catch(() => void 0);
38
+ }
15
39
  const stat = safeStat(source.path);
16
40
  return {
17
41
  source,
18
42
  offset: stat?.size ?? 0,
19
43
  ...stat ? { inode: stat.ino } : {},
20
- ...mobileIssueBuffer ? { mobileIssueBuffer } : {},
21
- ...extensionConsoleCapture ? { extensionConsoleCapture } : {}
44
+ ...mobileIssueBuffer ? { mobileIssueBuffer } : {}
22
45
  };
23
46
  }
47
+ function isRunDiagnosticsDocument(value) {
48
+ if (!isRecord(value) || value.schemaVersion !== 1 || value.scope !== "recipe-run-application") return false;
49
+ if (value.status !== "clean" && value.status !== "review" && value.status !== "unavailable") return false;
50
+ return typeof value.note === "string" && Array.isArray(value.findings);
51
+ }
24
52
  function finishRunDiagnostics(baseline, result) {
25
53
  if (!baseline) return result;
26
54
  try {
@@ -56,9 +84,7 @@ function finishRunDiagnostics(baseline, result) {
56
84
  }
57
85
  }
58
86
  function stopRunDiagnostics(baseline) {
59
- const capture = baseline?.extensionConsoleCapture;
60
- if (!capture || capture.exitCode !== null || capture.signalCode !== null) return;
61
- capture.kill("SIGTERM");
87
+ void baseline;
62
88
  }
63
89
  function collectRunDiagnostics(baseline, bufferedIssues) {
64
90
  const stat = safeStat(baseline.source.path);
@@ -157,39 +183,6 @@ function runMobileIssueCommand(projectRoot, command) {
157
183
  return null;
158
184
  }
159
185
  }
160
- async function startExtensionConsoleCapture(source, projectRoot) {
161
- const cdpPort = process.env.CDP_PORT ?? process.env.RECIPE_CDP_PORT;
162
- if (!cdpPort) return void 0;
163
- fs.mkdirSync(path.dirname(source.path), { recursive: true });
164
- const before = safeStat(source.path)?.size ?? 0;
165
- const script = path.join(runnerDir, "adapters", "extension", "console-tail.mjs");
166
- const child = spawn(process.execPath, [script, "--cdp-port", cdpPort, "--log", source.path], {
167
- cwd: projectRoot,
168
- stdio: "ignore"
169
- });
170
- const attached = await waitForExtensionAttachment(source.path, before, child, 3e3);
171
- if (attached) {
172
- await delay(50);
173
- return child;
174
- }
175
- if (child.exitCode === null && child.signalCode === null) child.kill("SIGTERM");
176
- return void 0;
177
- }
178
- async function waitForExtensionAttachment(logPath, offset, child, timeoutMs) {
179
- const deadline = Date.now() + timeoutMs;
180
- while (Date.now() < deadline && child.exitCode === null && child.signalCode === null) {
181
- const stat = safeStat(logPath);
182
- if (stat && stat.size > offset) {
183
- const appended = readSlice(logPath, offset, Math.min(stat.size - offset, 64 * 1024));
184
- if (appended.includes("[attached]")) return true;
185
- }
186
- await delay(50);
187
- }
188
- return false;
189
- }
190
- function delay(ms) {
191
- return new Promise((resolve) => setTimeout(resolve, ms));
192
- }
193
186
  function redactPreview(value) {
194
187
  return value.replace(/\b(Bearer)\s+\S+/giu, "$1 [REDACTED]").replace(/\b(password|passphrase|mnemonic|seed(?:Phrase)?|privateKey|secret|token|authorization)\b\s*[:=]\s*(?:"[^"]*"|'[^']*'|\S+)/giu, "$1=[REDACTED]").replace(/\b(?:0x)?[a-f0-9]{64,}\b/giu, "[REDACTED_HEX]").replace(/(https?:\/\/[^\s?]+)\?\S+/giu, "$1?[REDACTED_QUERY]").slice(0, MAX_PREVIEW_CHARS);
195
188
  }
@@ -267,5 +260,7 @@ export {
267
260
  beginRunDiagnostics,
268
261
  collectRunDiagnostics,
269
262
  finishRunDiagnostics,
263
+ formatRunDiagnosticsForHuman,
264
+ readRunDiagnosticsDocument,
270
265
  stopRunDiagnostics
271
266
  };
package/dist/runner.js CHANGED
@@ -58,7 +58,7 @@ async function createMetaMaskRunner(adapter, actionManifest, options = {}) {
58
58
  const logger = options.quietStdout ? new console.Console(process.stderr) : console;
59
59
  return createRecipeRunner({
60
60
  actionManifest,
61
- adapters: [...core, ...ui, ...lifecycle, ...custom],
61
+ adapters: withActionProgress([...core, ...ui, ...lifecycle, ...custom], options.onActionEvent),
62
62
  logger,
63
63
  recording: {
64
64
  targetProvider: createMetaMaskRecordingTargetProvider(adapter)
@@ -76,6 +76,36 @@ async function createMetaMaskRunner(adapter, actionManifest, options = {}) {
76
76
  }
77
77
  });
78
78
  }
79
+ function withActionProgress(adapters, onActionEvent) {
80
+ if (!onActionEvent) return adapters;
81
+ return adapters.map((entry) => ({
82
+ ...entry,
83
+ async execute(node, context) {
84
+ const event = { nodeId: context.nodeId, action: entry.action };
85
+ const reportProgress = !isAutomaticHudProgress(entry.action, node, context.nodeId);
86
+ if (reportProgress) onActionEvent({ ...event, status: "running" });
87
+ try {
88
+ const result = await entry.execute(node, context);
89
+ if (reportProgress) {
90
+ onActionEvent({ ...event, status: result.status === "fail" ? "failed" : "passed" });
91
+ }
92
+ return result;
93
+ } catch (error) {
94
+ if (reportProgress) onActionEvent({ ...event, status: "failed" });
95
+ throw error;
96
+ }
97
+ }
98
+ }));
99
+ }
100
+ function isAutomaticHudProgress(action, node, nodeId) {
101
+ if (action !== "app.hud" || node === null || typeof node !== "object" || Array.isArray(node)) {
102
+ return false;
103
+ }
104
+ const payload = node;
105
+ if (typeof payload.action_name === "string") return true;
106
+ const progress = payload.progress;
107
+ return nodeId === "recipe-complete" && progress !== null && typeof progress === "object" && !Array.isArray(progress) && progress.complete === true;
108
+ }
79
109
  function mobileSourceAwareLifecycleAdapters(adapter, lifecycle) {
80
110
  if (adapter !== "mobile") return lifecycle;
81
111
  return lifecycle.map((entry) => ({
@@ -120,5 +150,6 @@ export {
120
150
  createMetaMaskExtensionRunner,
121
151
  createMetaMaskMobileRunner,
122
152
  createMetaMaskRunner,
153
+ isAutomaticHudProgress,
123
154
  mobileSourceAwareLifecycleAdapters
124
155
  };
@@ -53,6 +53,32 @@ function readExistingContext(contextPath, repoRoot, runtimeDir, adapter) {
53
53
  return { context, existed: true };
54
54
  }
55
55
  function validExistingContext(context, repoRoot, runtimeDir, adapter) {
56
+ const allowedFields = /* @__PURE__ */ new Set([
57
+ "schemaVersion",
58
+ "repoRoot",
59
+ "project",
60
+ "slotId",
61
+ "machine",
62
+ "runtimeOwner",
63
+ "strict",
64
+ "platform",
65
+ "runtimeDir",
66
+ "gitDirty",
67
+ "simulator",
68
+ "adbSerial",
69
+ "distDir",
70
+ "extensionId",
71
+ "gitHead",
72
+ "gitBranch",
73
+ "gitCommit",
74
+ "createdAt",
75
+ "session",
76
+ "cdpPort",
77
+ "watcherPort",
78
+ "metroPort",
79
+ "devServerPort"
80
+ ]);
81
+ if (Object.keys(context).some((field) => !allowedFields.has(field))) return false;
56
82
  if (context.schemaVersion !== void 0 && context.schemaVersion !== 1) return false;
57
83
  if (context.repoRoot !== void 0 && (typeof context.repoRoot !== "string" || path.resolve(context.repoRoot) !== repoRoot)) return false;
58
84
  if (context.platform !== void 0 && context.platform !== adapter) return false;
@@ -82,14 +108,6 @@ function validExistingContext(context, repoRoot, runtimeDir, adapter) {
82
108
  const value = context[field];
83
109
  if (value !== void 0 && (!Number.isInteger(value) || Number(value) < 1 || Number(value) > 65535)) return false;
84
110
  }
85
- if (context.runtimeStart !== void 0) {
86
- if (!context.runtimeStart || typeof context.runtimeStart !== "object" || Array.isArray(context.runtimeStart)) return false;
87
- const runtimeStart = context.runtimeStart;
88
- if (runtimeStart.approved !== void 0 && typeof runtimeStart.approved !== "boolean") return false;
89
- for (const field of ["command", "readyUrl"]) {
90
- if (runtimeStart[field] !== void 0 && (typeof runtimeStart[field] !== "string" || runtimeStart[field].length === 0)) return false;
91
- }
92
- }
93
111
  const forbiddenResources = adapter === "core" ? ["cdpPort", "watcherPort", "metroPort", "devServerPort", "simulator", "adbSerial", "extensionId"] : adapter === "extension" ? ["metroPort", "simulator", "adbSerial"] : ["cdpPort", "extensionId"];
94
112
  if (forbiddenResources.some((field) => context[field] !== void 0)) return false;
95
113
  return true;
@@ -1,61 +1,48 @@
1
- # mm-harness — user cheatsheet
1
+ # mm-harness cheatsheet
2
2
 
3
- `mm-harness <command> [target] [flags]` · Run from inside any MetaMask checkout · Platform auto-detected
3
+ Run inside a MetaMask checkout. Product, slot, ports, and runtime paths are
4
+ auto-detected.
4
5
 
5
- ---
6
+ ## Operate
6
7
 
7
- ## Daily loop
8
-
9
- | Situation | Command |
8
+ | Need | Command |
10
9
  |---|---|
11
- | Start / relaunch iOS | `mm-harness launch ios` |
12
- | Start / relaunch Android | `mm-harness launch android` |
13
- | Launch extension (fullscreen) | `mm-harness launch` |
14
- | Launch extension in sidepanel | `mm-harness launch --sidepanel` |
15
- | Native code changed — rebuild first | `mm-harness launch ios --build` |
16
- | Tail Metro / webpack logs | `mm-harness logs` |
17
- | Open DevTools / RN inspector | `mm-harness debug` |
18
- | Seed wallet from fixture — no typing | `mm-harness fixtures set` |
19
- | Refresh wallet fixture files on slot | `mm-harness fixtures sync` |
20
-
21
- `launch` auto-installs the overlay on first use.
22
- `fixtures set` reads the password **from** the fixture — never typed.
23
-
24
- ---
25
-
26
- ## Proof loop
27
-
28
- ```
29
- actions → call <action> → flows → run recipe.json --plan → run recipe.json
10
+ | Extension | `mm-harness launch` |
11
+ | Extension sidepanel | `mm-harness launch --sidepanel` |
12
+ | iOS / Android | `mm-harness launch ios` / `mm-harness launch android` |
13
+ | Clean rebuild | `mm-harness launch --build` |
14
+ | Runtime status | `mm-harness status` |
15
+ | Logs | `mm-harness logs` |
16
+ | Debugger | `mm-harness debug` |
17
+ | Apply wallet fixture | `mm-harness fixtures set` |
18
+
19
+ Extension log sources: `extension`, `dapp`, `webpack`, `watcher`, `rebuild`.
20
+ Mobile log sources: `app`, `metro`.
21
+
22
+ ## Discover and prove
23
+
24
+ ```bash
25
+ mm-harness actions positions
26
+ mm-harness actions --action metamask.wallet.ensure_unlocked
27
+ mm-harness call metamask.wallet.ensure_unlocked
28
+ mm-harness flows
29
+ mm-harness run recipe.json --plan
30
+ mm-harness run recipe.json --artifacts-dir temp/recipe-proof
30
31
  ```
31
32
 
32
- | Command | What it does |
33
- |---|---|
34
- | `mm-harness actions` | Action vocabulary for this adapter |
35
- | `mm-harness call unlock` | One action through the real engine (fuzzy name ok) |
36
- | `mm-harness flows` | Reusable flows (personal > team > canonical) |
37
- | `mm-harness run recipe.json --plan` | Validate + print plan, no device touched; exit 5 if invalid |
38
- | `mm-harness run recipe.json` | Execute + write evidence (summary / trace / artifacts) |
39
-
40
- ---
33
+ The run result prints diagnostics plus absolute evidence paths. Resume safely with
34
+ `mm-harness last --json`.
41
35
 
42
- ## Health
36
+ ## Recover
43
37
 
44
- | Command | What it does |
45
- |---|---|
46
- | `mm-harness doctor` | Full readiness check — fixture, runtime context, overlay. No launch. |
47
- | `mm-harness doctor --fix` | Repair overlay + runtime context without launching the app. |
48
- | `mm-harness verify` | Check overlay/runtime is present and healthy. No launch. |
49
- | `mm-harness cleanup` | Remove the overlay and restore the checkout. |
50
-
51
- ---
52
-
53
- ## For agents and scripts
54
-
55
- **`--json`** — machine-readable output on every command; includes `recovered[]`, `mutations[]`, `schemaVersion`.
56
-
57
- **`--heal`** — `off` (fail fast, repro-preserving) · `infra-only` (heal transport, never wallet; `run`/`call` default) · `auto` (auto-ensure overlay + heal; `launch` default).
58
-
59
- **Exit codes** — `0` success · `1` recipe/action failed · `2` bad args / missing target · `5` validation failure (fix recipe).
38
+ ```bash
39
+ mm-harness doctor
40
+ mm-harness doctor --fix
41
+ mm-harness verify
42
+ mm-harness cleanup
43
+ mm-harness update
44
+ ```
60
45
 
61
- **Tab completion** `mm-harness completions install` prints the lines to add to `~/.zshrc` / `~/.bashrc`.
46
+ For automation, add `--json`. Use `--json-stream` for line-delimited run progress.
47
+ Bad input exits `2`; recipe/action failure exits `1`; recipe validation failure
48
+ exits `5`.
@@ -0,0 +1,32 @@
1
+ <!-- Generated by scripts/generate-cli-ergonomics-audit.mjs. Do not edit by hand. -->
2
+ # CLI ergonomics audit
3
+
4
+ This matrix tracks the public command surface. “Covered” means the shared preflight contract and
5
+ `tests/contract/cli-teaching-errors.test.sh` exercise the behavior without dispatching runtime work.
6
+ Runtime recovery is claimed only where command-specific failures return a stable `userAction`.
7
+ Success hints are intentionally limited to lifecycle transitions with one truthful next command;
8
+ discovery, read-only, and terminal evidence commands do not invent one. ID prefix support is N/A
9
+ unless a command displays a shortened ID that another command accepts.
10
+
11
+ | Command | Bad flag | First feedback | `--json` recovery | Success next step | Displayed ID prefix |
12
+ |---|---|---|---|---|---|
13
+ | `status` | covered: `CLI_UNKNOWN_OPTION` | N/A: no newly introduced slow path | usage + runtime `error.code/message/userAction` | structured `next` | N/A: command displays no shortened ID |
14
+ | `checklist` | covered: `CLI_UNKNOWN_OPTION` | N/A: no newly introduced slow path | usage errors covered; no runtime recovery claim | N/A: discovery, read-only, or terminal result | N/A: command displays no shortened ID |
15
+ | `actions` | covered: `CLI_UNKNOWN_OPTION` | N/A: no newly introduced slow path | usage + runtime `error.code/message/userAction` | N/A: discovery, read-only, or terminal result | N/A: command displays no shortened ID |
16
+ | `stop` | covered: `CLI_UNKNOWN_OPTION` | N/A: no newly introduced slow path | usage + runtime `error.code/message/userAction` | launch `next` when the adapter has one unambiguous launch | N/A: command displays no shortened ID |
17
+ | `call` | covered: `CLI_UNKNOWN_OPTION` | N/A: no newly introduced slow path | usage + runtime `error.code/message/userAction` | N/A: discovery, read-only, or terminal result | N/A: command displays no shortened ID |
18
+ | `flows` | covered: `CLI_UNKNOWN_OPTION` | N/A: no newly introduced slow path | usage errors covered; no runtime recovery claim | N/A: discovery, read-only, or terminal result | N/A: command displays no shortened ID |
19
+ | `run` | covered: `CLI_UNKNOWN_OPTION` | TTY intent before slow work; machine output clean | usage + runtime `error.code/message/userAction` | N/A: discovery, read-only, or terminal result | N/A: command displays no shortened ID |
20
+ | `last` | covered: `CLI_UNKNOWN_OPTION` | N/A: no newly introduced slow path | usage + runtime `error.code/message/userAction` | N/A: discovery, read-only, or terminal result | N/A: command displays no shortened ID |
21
+ | `doctor` | covered: `CLI_UNKNOWN_OPTION` | N/A: no newly introduced slow path | usage + runtime `error.code/message/userAction` | N/A: discovery, read-only, or terminal result | N/A: command displays no shortened ID |
22
+ | `check` | covered: `CLI_UNKNOWN_OPTION` | N/A: no newly introduced slow path | usage + runtime `error.code/message/userAction` | N/A: discovery, read-only, or terminal result | N/A: command displays no shortened ID |
23
+ | `recipe-quality` | covered: `CLI_UNKNOWN_OPTION` | N/A: no newly introduced slow path | usage + runtime `error.code/message/userAction` | N/A: discovery, read-only, or terminal result | N/A: command displays no shortened ID |
24
+ | `provision` | covered: `CLI_UNKNOWN_OPTION` | TTY intent before slow work; machine output clean | usage + runtime `error.code/message/userAction` | launch `next` after install | N/A: command displays no shortened ID |
25
+ | `install` | covered: `CLI_UNKNOWN_OPTION` | N/A: no newly introduced slow path | usage + runtime `error.code/message/userAction` | verify `next` | N/A: command displays no shortened ID |
26
+ | `verify` | covered: `CLI_UNKNOWN_OPTION` | N/A: no newly introduced slow path | usage + runtime `error.code/message/userAction` | status `next` | N/A: command displays no shortened ID |
27
+ | `cleanup` | covered: `CLI_UNKNOWN_OPTION` | N/A: no newly introduced slow path | usage + runtime `error.code/message/userAction` | install `next` | N/A: command displays no shortened ID |
28
+ | `launch` | covered: `CLI_UNKNOWN_OPTION` | TTY intent before slow work; machine output clean | usage + runtime `error.code/message/userAction` | N/A: discovery, read-only, or terminal result | N/A: command displays no shortened ID |
29
+ | `logs` | covered: `CLI_UNKNOWN_OPTION` | N/A: no newly introduced slow path | usage + runtime `error.code/message/userAction` | N/A: discovery, read-only, or terminal result | N/A: command displays no shortened ID |
30
+ | `debug` | covered: `CLI_UNKNOWN_OPTION` | N/A: no newly introduced slow path | usage errors covered; no runtime recovery claim | N/A: discovery, read-only, or terminal result | N/A: command displays no shortened ID |
31
+ | `update` | covered: `CLI_UNKNOWN_OPTION` | N/A: no newly introduced slow path | usage + runtime `error.code/message/userAction` | N/A: discovery, read-only, or terminal result | N/A: command displays no shortened ID |
32
+ | `fixtures` | covered: `CLI_UNKNOWN_OPTION` | N/A: no newly introduced slow path | usage + runtime `error.code/message/userAction` | N/A: discovery, read-only, or terminal result | N/A: command displays no shortened ID |
@@ -0,0 +1,48 @@
1
+ # Human QA
2
+
3
+ Run this from each product checkout before a release. Verify the visible outcome,
4
+ not only the exit code.
5
+
6
+ ## Common
7
+
8
+ - [ ] `mm-harness --version` prints the candidate version.
9
+ - [ ] `mm-harness doctor` detects the correct product and reports only current
10
+ configuration.
11
+ - [ ] A misspelled flag explains the error and gives a runnable next command.
12
+ - [ ] `mm-harness actions` and `mm-harness flows` return useful, bounded discovery.
13
+ - [ ] `mm-harness fixtures set` either applies the fixture or names the missing
14
+ file and exact recovery action.
15
+ - [ ] `mm-harness run <safe-recipe> --artifacts-dir temp/qa-proof` prints findings,
16
+ diagnostics, and absolute evidence paths.
17
+ - [ ] `mm-harness last --json` identifies that run without rerunning it.
18
+ - [ ] `mm-harness doctor --json` is one parseable document with no human decoration.
19
+
20
+ ## Extension
21
+
22
+ - [ ] `mm-harness launch` opens/reuses the checkout's named Chrome profile.
23
+ - [ ] `mm-harness launch --sidepanel` opens the MetaMask sidepanel without extra
24
+ Extension tabs.
25
+ - [ ] `mm-harness debug` opens connected DevTools for the active MetaMask target.
26
+ - [ ] `mm-harness logs --source extension` shows Extension console events.
27
+ - [ ] `mm-harness logs --source dapp` shows dapp console events separately.
28
+ - [ ] Edit a visible source string twice, waiting for successful incremental
29
+ compilation after each edit; refresh the browser and confirm both changes.
30
+ - [ ] Revert the edit, confirm a third incremental compilation, and verify the
31
+ same watcher and Chrome process were reused.
32
+
33
+ ## Mobile
34
+
35
+ - [ ] `mm-harness launch ios` boots/reuses the intended simulator and opens the app.
36
+ - [ ] `mm-harness launch android` targets the intended emulator/device.
37
+ - [ ] `mm-harness logs --source metro` and `--source app` show distinct streams.
38
+ - [ ] `mm-harness debug` opens the usable React Native developer surface.
39
+ - [ ] Change a visible JS string, confirm Metro rebuilds, reload the app, and see
40
+ the change without a native rebuild.
41
+
42
+ ## Core
43
+
44
+ - [ ] `mm-harness status`, `doctor`, `actions`, `flows`, and a safe recipe run work
45
+ without browser/device assumptions.
46
+
47
+ Record the command, version, checkout SHA, visible mismatch, exit code, and JSON
48
+ error envelope for any failure.
package/docs/CODE-MAP.md CHANGED
@@ -11,7 +11,7 @@ bin/mm-harness bash front door: MM_HARNESS_BIN dev/prod switch,
11
11
  │ stale-dist warning), deps ensure
12
12
 
13
13
  src/mm-harness-cli.ts commander surface: grouped colored help, per-command
14
- │ --help, retired-name teaching; delegates in-process
14
+ │ --help and usage errors; delegates in-process
15
15
 
16
16
  src/cli.ts parse + dispatch ONLY — no verb logic lives here
17
17
 
@@ -27,6 +27,8 @@ The CLI answers relative to the checkout it is standing in.
27
27
  Long operations show intent immediately and progress continuously.
28
28
  - First line within milliseconds: what is being done, where (device/port), and
29
29
  where the full log lives.
30
+ - Add an explicit first line only when work can block before existing output. Fast
31
+ commands use their result as acknowledgement; they do not print a redundant banner.
30
32
  - Leaf output streams live to stderr — `--json` reserves stdout for the envelope,
31
33
  it never buys silence. Quiet stretches get heartbeats with elapsed time.
32
34
  - Each dev server a command starts gets its tmux log-tail window at launch time
@@ -0,0 +1,62 @@
1
+ # Video demo validation
2
+
3
+ Candidate: local packed `@deeeed/metamask-harness` 0.17.1 installed globally.
4
+ Run commands from the named checkout; product/ports are auto-detected.
5
+
6
+ ## Common
7
+
8
+ - [x] `mm-harness --version` prints `0.17.1` from the global npm install.
9
+ - [x] `yarn check` passes.
10
+ - [x] No `package-lock.json`; Yarn state is valid.
11
+ - [x] Current source/docs contain no compatibility or removed-command paths.
12
+ - [x] `doctor` reports current runtime fields only.
13
+ - [x] Recipe results print diagnostics and absolute artifact paths inline.
14
+
15
+ ## Extension (`mme-2`)
16
+
17
+ - [x] `mm-harness doctor` reports healthy CDP and active webpack watcher.
18
+ - [x] `mm-harness run runner.smoke --artifacts-dir temp/final-demo-smoke` passes.
19
+ - [x] Smoke prints six absolute evidence paths and `diagnostics: CLEAN`.
20
+ - [x] `mm-harness debug` connects native DevTools to the active slot target.
21
+ - [x] Fullscreen and sidepanel targets are selectable.
22
+ - [x] Two source edits plus revert compiled incrementally with the same watcher;
23
+ browser refresh showed each change without relaunch.
24
+
25
+ ## Mobile (`mm-1`, iOS)
26
+
27
+ - [x] `mm-harness install` writes metadata only; product checkout remains clean.
28
+ - [x] `mm-harness fixtures set` reads the canonical fixture and unlocks `dev1`.
29
+ - [x] `mm-harness call metamask.wallet.ensure_unlocked` passes from the global package.
30
+ - [x] `mm-harness verify` passes after fixture setup.
31
+ - [x] `mm-harness status` reports `WalletView`, unlocked wallet, selected account,
32
+ and fixture `READY`.
33
+ - [ ] During the recording, visually confirm the expected simulator is foregrounded.
34
+
35
+ ## Core (`core-1`)
36
+
37
+ - [x] `mm-harness doctor` reports the headless controller ready.
38
+ - [x] Canonical wallet fixture is present.
39
+ - [x] `mm-harness run perps.smoke --artifacts-dir temp/final-demo-smoke` passes.
40
+ - [x] Positions, orders, and account state are read through the controller.
41
+ - [x] Evidence prints absolute paths; diagnostics correctly reports `N/A` for headless Core.
42
+
43
+ ## Recording sequence
44
+
45
+ ```bash
46
+ # Extension
47
+ cd /Users/deeeed/farmslot/repos/mme-2
48
+ mm-harness doctor
49
+ mm-harness actions positions
50
+ mm-harness flows
51
+ mm-harness run runner.smoke --artifacts-dir temp/demo
52
+
53
+ # Mobile
54
+ cd /Users/deeeed/farmslot/repos/mm-1
55
+ mm-harness launch ios
56
+ mm-harness fixtures set
57
+ mm-harness call metamask.wallet.ensure_unlocked
58
+
59
+ # Core
60
+ cd /Users/deeeed/farmslot/repos/core-1
61
+ mm-harness run perps.smoke --artifacts-dir temp/demo
62
+ ```