@deeeed/metamask-harness 0.17.4 → 0.17.5

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 (114) hide show
  1. package/CHANGELOG.md +6 -103
  2. package/README.md +136 -96
  3. package/adapters/extension/console-tail.mjs +24 -55
  4. package/adapters/extension/ensure-browser.sh +7 -1
  5. package/adapters/extension/inject.mjs +15 -5
  6. package/adapters/extension/launch-browser.cjs +4 -3
  7. package/adapters/extension/launch.sh +11 -8
  8. package/adapters/extension/lib/chrome-args.cjs +24 -0
  9. package/adapters/extension/live.sh +1 -2
  10. package/adapters/extension/reattach.sh +0 -1
  11. package/adapters/extension/seed-fixture.sh +12 -4
  12. package/adapters/extension/sidepanel-toggle.sh +1 -4
  13. package/adapters/extension/start-watch.sh +4 -17
  14. package/adapters/extension/stop-viewers.sh +1 -1
  15. package/adapters/extension/verify.sh +1 -1
  16. package/adapters/manifest.json +45 -53
  17. package/adapters/mobile/bridge-runtime/cdp-bridge.cjs +0 -16
  18. package/adapters/mobile/bridge-runtime/console-forwarder.cjs +1 -1
  19. package/adapters/mobile/bridge-runtime/lib/match-bridge-target.cjs +1 -3
  20. package/adapters/mobile/bridge-runtime/lib/target-discovery.cjs +3 -2
  21. package/adapters/mobile/cleanup.sh +176 -16
  22. package/adapters/mobile/inject.sh +671 -90
  23. package/adapters/mobile/overlay/app/dev-tools/AgenticService/AgentStepHud.tsx.patch +185 -0
  24. package/adapters/mobile/overlay/app/dev-tools/AgenticService/AgenticService.ts.patch +1662 -0
  25. package/adapters/mobile/overlay/compat/README.md +28 -0
  26. package/adapters/mobile/overlay/compat/rn81-message-event-source.patch +42 -0
  27. package/adapters/mobile/start-metro.sh +41 -0
  28. package/adapters/mobile/verify.sh +61 -4
  29. package/adapters/mobile/wait-for-bridge.sh +8 -22
  30. package/adapters/shared/harness-source-fingerprint.mjs +49 -0
  31. package/adapters/shared/install-repo-deps.sh +1 -5
  32. package/adapters/shared/open-debug.mjs +184 -103
  33. package/adapters/shared/resolve-farmslot-ports-core.mjs +3 -0
  34. package/adapters/shared/resolve-farmslot-ports.mjs +5 -0
  35. package/adapters/shared/resolve-farmslot-ports.sh +7 -0
  36. package/adapters/shared/resolve-slot-ports-core.mjs +6 -0
  37. package/adapters/shared/resolve-slot-ports.sh +7 -2
  38. package/bin/mm-harness +0 -7
  39. package/dist/adapters/extension/harness-freshness.js +39 -0
  40. package/dist/adapters/extension/runtime-decision.js +4 -73
  41. package/dist/adapters/extension/surface.js +2 -3
  42. package/dist/adapters/mobile/perps-env.js +3 -43
  43. package/dist/adapters/mobile/prepare.js +7 -22
  44. package/dist/adapters/mobile/source-freshness.js +26 -41
  45. package/dist/adapters/mobile/surface.js +1 -1
  46. package/dist/adapters/resolve-slot-ports.js +2 -0
  47. package/dist/adapters/slot-ports.js +0 -16
  48. package/dist/adapters.js +5 -12
  49. package/dist/cli-commands.js +1 -1
  50. package/dist/cli.js +12 -6
  51. package/dist/commands/call.js +22 -56
  52. package/dist/commands/check.js +3 -9
  53. package/dist/commands/checklist.js +4 -1
  54. package/dist/commands/debug.js +31 -38
  55. package/dist/commands/device-target.js +12 -27
  56. package/dist/commands/doctor.js +23 -77
  57. package/dist/commands/fixtures.js +17 -27
  58. package/dist/commands/launch/extension.js +2 -0
  59. package/dist/commands/launch/index.js +59 -167
  60. package/dist/commands/logs.js +6 -8
  61. package/dist/commands/manifest.js +25 -151
  62. package/dist/commands/parse-args.js +0 -2
  63. package/dist/commands/provision.js +3 -10
  64. package/dist/commands/run-engine.js +69 -71
  65. package/dist/commands/run-report.js +3 -12
  66. package/dist/commands/run.js +35 -245
  67. package/dist/commands/shared.js +3 -55
  68. package/dist/commands/status-probe.js +1 -1
  69. package/dist/commands/status.js +1 -1
  70. package/dist/commands/stop.js +2 -7
  71. package/dist/commands/update.js +28 -16
  72. package/dist/doctor.js +4 -1
  73. package/dist/harness.js +134 -43
  74. package/dist/heal-bounds.js +1 -1
  75. package/dist/live-adapter-contract.js +20 -6
  76. package/dist/mm-harness-cli.js +48 -123
  77. package/dist/paths.js +5 -2
  78. package/dist/runner.js +2 -37
  79. package/dist/runtime-context.js +8 -26
  80. package/docs/ADAPTER-SURFACE.md +112 -19
  81. package/docs/CHEATSHEET.md +52 -39
  82. package/docs/CLI-SPEC.md +1098 -0
  83. package/docs/CODE-MAP.md +1 -1
  84. package/docs/DEBUG-HANDOVER.md +36 -0
  85. package/docs/MENTAL-MODEL.md +295 -0
  86. package/docs/UX-PRINCIPLES.md +1 -3
  87. package/docs/architecture.md +375 -65
  88. package/docs/live-adapter-contract.md +2 -4
  89. package/docs/recipe-libraries.md +275 -45
  90. package/library/README.md +0 -4
  91. package/library/actions/core/perps/_controller.mjs +55 -10
  92. package/library/actions/mobile/platform/bridge.mjs +1 -5
  93. package/library/manifests/core.action-manifest.json +7 -7
  94. package/library/manifests/mobile.action-manifest.json +3 -28
  95. package/package.json +1 -4
  96. package/adapters/extension/launch-webpack.cjs +0 -83
  97. package/adapters/extension/stamp-runtime-title.cjs +0 -58
  98. package/adapters/extension/sync-webpack-dist.cjs +0 -107
  99. package/adapters/mobile/launch-console-forwarder.cjs +0 -39
  100. package/adapters/mobile/start-console-forwarder.sh +0 -70
  101. package/adapters/shared/update-check-worker.mjs +0 -43
  102. package/dist/adapters/extension/console-capture.js +0 -93
  103. package/dist/command-contract.js +0 -441
  104. package/dist/command-journal.js +0 -225
  105. package/dist/commands/last.js +0 -52
  106. package/dist/json-stream.js +0 -57
  107. package/dist/run-diagnostics.js +0 -261
  108. package/docs/CLI-ERGONOMICS-AUDIT.md +0 -32
  109. package/docs/CLI-ERGONOMICS-HUMAN-QA.md +0 -64
  110. package/docs/VIDEO-DEMO-VALIDATION.md +0 -74
  111. package/library/recipes/runner/smoke.core.recipe.json +0 -27
  112. package/library/recipes/wallet/smoke.extension.recipe.json +0 -42
  113. package/library/recipes/wallet/smoke.mobile.recipe.json +0 -42
  114. package/scripts/validate-human-outcomes.mjs +0 -169
@@ -1,52 +0,0 @@
1
- import { readCommandJournal } from "../command-journal.js";
2
- import { EXIT } from "./shared.js";
3
- import {
4
- optionFlag,
5
- optionString,
6
- targetPath
7
- } from "./parse-args.js";
8
- async function handleLast({ options }) {
9
- const target = targetPath(options);
10
- const { file, record } = readCommandJournal(target, optionString(options, "runtimeDir"));
11
- const json = optionFlag(options, "json");
12
- if (!record) {
13
- const message = `no resumability journal exists for ${target}`;
14
- const userAction = "run a proof or runtime command in this checkout, then re-run mm-harness last --json";
15
- if (json) {
16
- console.log(JSON.stringify({
17
- schemaVersion: 1,
18
- command: "last",
19
- target,
20
- journalPath: file,
21
- status: "fail",
22
- exitCode: EXIT.runtime,
23
- error: { code: "LAST_NOT_FOUND", message, userAction }
24
- }, null, 2));
25
- } else {
26
- console.error(`\u2717 mm-harness last: ${message}
27
- Next: ${userAction}`);
28
- }
29
- return EXIT.runtime;
30
- }
31
- if (json) {
32
- console.log(JSON.stringify({
33
- schemaVersion: 1,
34
- command: "last",
35
- target,
36
- journalPath: file,
37
- status: "pass",
38
- exitCode: EXIT.ok,
39
- last: record
40
- }, null, 2));
41
- } else {
42
- const end = record.finishedAt ?? "still running or interrupted";
43
- console.log(`${record.verdict.toUpperCase()} ${record.command} (exit ${record.exitCode ?? "pending"})`);
44
- console.log(`started: ${record.startedAt}`);
45
- console.log(`finished: ${end}`);
46
- if (record.evidencePaths.length > 0) console.log(`evidence: ${record.evidencePaths.join(", ")}`);
47
- }
48
- return EXIT.ok;
49
- }
50
- export {
51
- handleLast
52
- };
@@ -1,57 +0,0 @@
1
- class JsonStreamWriter {
2
- enabled;
3
- command;
4
- completed = false;
5
- writeLine;
6
- constructor(command, enabled, output = process.stdout) {
7
- this.command = command;
8
- this.enabled = enabled;
9
- const write = output.write.bind(output);
10
- this.writeLine = (line) => {
11
- write(`${line}
12
- `);
13
- };
14
- }
15
- isolateStdout() {
16
- if (!this.enabled) return () => void 0;
17
- const original = process.stdout.write;
18
- const redirect = ((chunk, encoding, callback) => process.stderr.write(chunk, encoding, callback));
19
- process.stdout.write = redirect;
20
- return () => {
21
- if (process.stdout.write === redirect) process.stdout.write = original;
22
- };
23
- }
24
- emit(event, fields = {}) {
25
- if (!this.enabled || this.completed) return;
26
- this.writeLine(JSON.stringify({
27
- schemaVersion: 1,
28
- command: this.command,
29
- event,
30
- ...fields,
31
- ts: (/* @__PURE__ */ new Date()).toISOString()
32
- }));
33
- }
34
- phase(phase, fields = {}) {
35
- this.emit("phase", { phase, ...fields });
36
- }
37
- node(nodeId, action, status) {
38
- this.emit("node", { nodeId, action, status });
39
- }
40
- mutation(mutation) {
41
- this.emit("mutation", { mutation });
42
- }
43
- recovery(code) {
44
- this.emit("recovery", { code });
45
- }
46
- error(error) {
47
- this.emit("error", { error });
48
- }
49
- complete(status, exitCode, fields = {}) {
50
- if (!this.enabled || this.completed) return;
51
- this.emit("complete", { status, exitCode, ...fields });
52
- this.completed = true;
53
- }
54
- }
55
- export {
56
- JsonStreamWriter
57
- };
@@ -1,261 +0,0 @@
1
- import fs from "node:fs";
2
- import path from "node:path";
3
- import { createHash } from "node:crypto";
4
- import { spawnSync } from "node:child_process";
5
- import { getAdapterSurface } from "./adapters/surface.js";
6
- import { runnerDir } from "./paths.js";
7
- import { ensureExtensionConsoleCapture } from "./adapters/extension/console-capture.js";
8
- const MAX_CAPTURE_BYTES = 512 * 1024;
9
- const MAX_FINDINGS = 20;
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
- }
32
- async function beginRunDiagnostics(adapter, projectRoot) {
33
- const source = getAdapterSurface(adapter).appLogSource(projectRoot);
34
- if (!source) return null;
35
- const mobileIssueBuffer = adapter === "mobile" && armMobileIssueBuffer(projectRoot) ? { projectRoot } : void 0;
36
- if (adapter === "extension") {
37
- await ensureExtensionConsoleCapture(projectRoot).catch(() => void 0);
38
- }
39
- const stat = safeStat(source.path);
40
- return {
41
- source,
42
- offset: stat?.size ?? 0,
43
- ...stat ? { inode: stat.ino } : {},
44
- ...mobileIssueBuffer ? { mobileIssueBuffer } : {}
45
- };
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
- }
52
- function finishRunDiagnostics(baseline, result) {
53
- if (!baseline) return result;
54
- try {
55
- const bufferedIssues = baseline.mobileIssueBuffer ? collectMobileIssueBuffer(baseline.mobileIssueBuffer.projectRoot) : void 0;
56
- const diagnostics = collectRunDiagnostics(baseline, bufferedIssues);
57
- const artifactsDir = path.dirname(result.summaryPath);
58
- const diagnosticsPath = path.join(artifactsDir, "diagnostics.json");
59
- fs.writeFileSync(diagnosticsPath, `${JSON.stringify(diagnostics, null, 2)}
60
- `);
61
- indexDiagnosticArtifact(result.artifactManifestPath);
62
- indexDiagnosticSummary(result.summaryPath, diagnostics);
63
- return {
64
- ...result,
65
- diagnosticsPath,
66
- sideFindings: {
67
- status: diagnostics.status,
68
- note: diagnostics.note,
69
- counts: diagnostics.counts
70
- }
71
- };
72
- } catch (error) {
73
- const detail = error instanceof Error ? error.message : String(error);
74
- console.warn(`WARN: run diagnostics were unavailable: ${detail}`);
75
- return {
76
- ...result,
77
- sideFindings: {
78
- status: "unavailable",
79
- note: "Run diagnostics could not be collected; the recipe result is unchanged.",
80
- counts: { total: 0, warning: 0, error: 0, exception: 0 }
81
- }
82
- };
83
- }
84
- }
85
- function collectRunDiagnostics(baseline, bufferedIssues) {
86
- const stat = safeStat(baseline.source.path);
87
- const source = {
88
- label: baseline.source.label,
89
- path: baseline.source.path,
90
- startOffset: baseline.offset,
91
- endOffset: stat?.size ?? baseline.offset,
92
- bytesRead: 0,
93
- truncated: false,
94
- inAppBuffer: bufferedIssues === void 0 ? "n/a" : bufferedIssues === null ? "unavailable" : "collected"
95
- };
96
- let text = "";
97
- if (stat) {
98
- const sameFile = baseline.inode === void 0 || baseline.inode === stat.ino;
99
- const startOffset = sameFile && stat.size >= baseline.offset ? baseline.offset : 0;
100
- const available = Math.max(0, stat.size - startOffset);
101
- const bytesToRead = Math.min(available, MAX_CAPTURE_BYTES);
102
- source.startOffset = startOffset;
103
- source.endOffset = stat.size;
104
- source.bytesRead = bytesToRead;
105
- source.truncated = available > MAX_CAPTURE_BYTES;
106
- if (bytesToRead > 0) text = readSlice(baseline.source.path, startOffset, bytesToRead);
107
- }
108
- const allFindings = dedupeFindings(
109
- [
110
- ...text.split(/\r?\n/u).map(classifyLine).filter((finding) => finding !== null),
111
- ...(bufferedIssues ?? []).map(classifyBufferedIssue).filter((finding) => finding !== null)
112
- ]
113
- );
114
- const findings = allFindings.slice(0, MAX_FINDINGS);
115
- const counts = countFindings(allFindings);
116
- const status = counts.total > 0 ? "review" : stat || bufferedIssues !== void 0 && bufferedIssues !== null ? "clean" : "unavailable";
117
- const note = counts.total > 0 ? `Observed ${counts.total} distinct application warning/error event(s) during the recipe run; relation to the task is not determined.` : status === "clean" ? "No application warnings or errors were emitted during the recipe run." : "Application diagnostics were unavailable for this run.";
118
- return {
119
- schemaVersion: 1,
120
- scope: "recipe-run-application",
121
- status,
122
- nonBlocking: true,
123
- note,
124
- source,
125
- counts,
126
- findings
127
- };
128
- }
129
- function classifyLine(line) {
130
- const trimmed = line.trim();
131
- if (!trimmed) return null;
132
- let level = null;
133
- if (/\bEXCEPTION\s{2,}|\b(?:Uncaught|UnhandledPromiseRejection|FATAL EXCEPTION)\b/iu.test(trimmed)) {
134
- level = "exception";
135
- } else if (/^(?:ERROR\s{2,}|\[error\]\s*)/iu.test(trimmed) || /\]\s+ERROR\s{2,}/u.test(trimmed)) {
136
- level = "error";
137
- } else if (/^(?:WARN(?:ING)?\s{2,}|\[warn(?:ing)?\]\s*)/iu.test(trimmed) || /\]\s+WARN(?:ING)?\s{2,}/u.test(trimmed)) {
138
- level = "warning";
139
- }
140
- if (!level) return null;
141
- return makeFinding(level, trimmed);
142
- }
143
- function classifyBufferedIssue(value) {
144
- if (!isRecord(value) || typeof value.text !== "string") return null;
145
- const rawLevel = String(value.level ?? "").toLowerCase();
146
- const level = rawLevel === "warn" || rawLevel === "warning" ? "warning" : rawLevel === "error" ? "error" : rawLevel === "exception" ? "exception" : null;
147
- return level ? makeFinding(level, value.text) : null;
148
- }
149
- function makeFinding(level, text) {
150
- const preview = redactPreview(text.trim());
151
- const identity = preview.replace(/\b\d{2}:\d{2}:\d{2}(?:\.\d{1,3})?\b/gu, "[TIME]").replace(/\[(?:sw|page:[^\]]+|console:[^\]]+)\]/gu, "[APP]").replace(/^(?:(?:WARN(?:ING)?|ERROR|EXCEPTION|\[TIME\]|\[APP\])\s*)+/u, "");
152
- return {
153
- level,
154
- fingerprint: createHash("sha256").update(`${level}|${identity}`).digest("hex").slice(0, 12),
155
- preview
156
- };
157
- }
158
- function armMobileIssueBuffer(projectRoot) {
159
- const armed = runMobileIssueCommand(projectRoot, "issues-arm");
160
- if (!armed) return false;
161
- return runMobileIssueCommand(projectRoot, "issues-collect") !== null;
162
- }
163
- function collectMobileIssueBuffer(projectRoot) {
164
- const result = runMobileIssueCommand(projectRoot, "issues-collect");
165
- return isRecord(result) && Array.isArray(result.entries) ? result.entries : null;
166
- }
167
- function runMobileIssueCommand(projectRoot, command) {
168
- const bridge = path.join(runnerDir, "adapters", "mobile", "bridge-runtime", "cdp-bridge.cjs");
169
- const result = spawnSync(process.execPath, [bridge, command], {
170
- cwd: projectRoot,
171
- env: { ...process.env, APP_ROOT: projectRoot, CDP_TIMEOUT: "5000" },
172
- encoding: "utf8",
173
- timeout: 1e4
174
- });
175
- if (result.status !== 0) return null;
176
- try {
177
- return JSON.parse(result.stdout.trim());
178
- } catch {
179
- return null;
180
- }
181
- }
182
- function redactPreview(value) {
183
- 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);
184
- }
185
- function dedupeFindings(findings) {
186
- const byFingerprint = /* @__PURE__ */ new Map();
187
- for (const finding of findings) {
188
- const existing = byFingerprint.get(finding.fingerprint);
189
- if (existing) existing.count += 1;
190
- else byFingerprint.set(finding.fingerprint, { ...finding, count: 1 });
191
- }
192
- return [...byFingerprint.values()];
193
- }
194
- function countFindings(findings) {
195
- const counts = { total: findings.length, warning: 0, error: 0, exception: 0 };
196
- for (const finding of findings) counts[finding.level] += 1;
197
- return counts;
198
- }
199
- function readSlice(filePath, offset, length) {
200
- const fd = fs.openSync(filePath, "r");
201
- try {
202
- const buffer = Buffer.alloc(length);
203
- const bytesRead = fs.readSync(fd, buffer, 0, length, offset);
204
- return buffer.subarray(0, bytesRead).toString("utf8");
205
- } finally {
206
- fs.closeSync(fd);
207
- }
208
- }
209
- function safeStat(filePath) {
210
- try {
211
- return fs.statSync(filePath);
212
- } catch {
213
- return null;
214
- }
215
- }
216
- function indexDiagnosticArtifact(manifestPath) {
217
- const manifest = readJsonRecord(manifestPath);
218
- if (!manifest) return;
219
- const artifacts = Array.isArray(manifest.artifacts) ? manifest.artifacts : [];
220
- manifest.artifacts = [
221
- ...artifacts.filter((artifact) => !isRecord(artifact) || artifact.path !== "diagnostics.json"),
222
- {
223
- path: "diagnostics.json",
224
- type: "json",
225
- label: "Run-scoped application diagnostics",
226
- category: "diagnostic"
227
- }
228
- ];
229
- fs.writeFileSync(manifestPath, `${JSON.stringify(manifest, null, 2)}
230
- `);
231
- }
232
- function indexDiagnosticSummary(summaryPath, diagnostics) {
233
- const summary = readJsonRecord(summaryPath);
234
- if (!summary) return;
235
- summary.sideFindings = {
236
- status: diagnostics.status,
237
- nonBlocking: true,
238
- counts: diagnostics.counts,
239
- diagnosticsPath: "diagnostics.json"
240
- };
241
- fs.writeFileSync(summaryPath, `${JSON.stringify(summary, null, 2)}
242
- `);
243
- }
244
- function readJsonRecord(filePath) {
245
- try {
246
- const value = JSON.parse(fs.readFileSync(filePath, "utf8"));
247
- return isRecord(value) ? value : null;
248
- } catch {
249
- return null;
250
- }
251
- }
252
- function isRecord(value) {
253
- return typeof value === "object" && value !== null && !Array.isArray(value);
254
- }
255
- export {
256
- beginRunDiagnostics,
257
- collectRunDiagnostics,
258
- finishRunDiagnostics,
259
- formatRunDiagnosticsForHuman,
260
- readRunDiagnosticsDocument
261
- };
@@ -1,32 +0,0 @@
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 |
@@ -1,64 +0,0 @@
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
- Run the non-visual gate first:
7
-
8
- ```bash
9
- yarn qa:human \
10
- --extension /path/to/metamask-extension \
11
- --mobile /path/to/metamask-mobile \
12
- --core /path/to/metamask-core \
13
- --artifacts-dir temp/human-qa
14
- ```
15
-
16
- It verifies live readiness, installed runtime, debugger target resolution, smoke
17
- evidence, and unchanged tracked product source. The checks below cover outcomes
18
- that still require eyes or an intentional source edit.
19
-
20
- ## Common
21
-
22
- - [ ] `mm-harness --version` prints the candidate version.
23
- - [ ] `mm-harness doctor` detects the correct product and reports only current
24
- configuration.
25
- - [ ] Without `capture-helper`, `doctor` names the CDP or simulator/ADB screenshot
26
- fallback and says video is unavailable.
27
- - [ ] A misspelled flag explains the error and gives a runnable next command.
28
- - [ ] `mm-harness actions` and `mm-harness flows` return useful, bounded discovery.
29
- - [ ] `mm-harness fixtures set` either applies the fixture or names the missing
30
- file and exact recovery action.
31
- - [ ] `mm-harness run <safe-recipe> --artifacts-dir temp/qa-proof` prints findings,
32
- diagnostics, and absolute evidence paths.
33
- - [ ] `mm-harness last --json` identifies that run without rerunning it.
34
- - [ ] `mm-harness doctor --json` is one parseable document with no human decoration.
35
-
36
- ## Extension
37
-
38
- - [ ] `mm-harness launch` opens/reuses the checkout's named Chrome profile.
39
- - [ ] `mm-harness launch --sidepanel` opens the MetaMask sidepanel without extra
40
- Extension tabs.
41
- - [ ] `mm-harness debug` opens connected DevTools for the active MetaMask target.
42
- - [ ] `mm-harness logs --source extension` shows Extension console events.
43
- - [ ] `mm-harness logs --source dapp` shows dapp console events separately.
44
- - [ ] Edit a visible source string twice, waiting for successful incremental
45
- compilation after each edit; refresh the browser and confirm both changes.
46
- - [ ] Revert the edit, confirm a third incremental compilation, and verify the
47
- same watcher and Chrome process were reused.
48
-
49
- ## Mobile
50
-
51
- - [ ] `mm-harness launch ios` boots/reuses the intended simulator and opens the app.
52
- - [ ] `mm-harness launch android` targets the intended emulator/device.
53
- - [ ] `mm-harness logs --source metro` and `--source app` show distinct streams.
54
- - [ ] `mm-harness debug` opens the usable React Native developer surface.
55
- - [ ] Change a visible JS string, confirm Metro rebuilds, reload the app, and see
56
- the change without a native rebuild.
57
-
58
- ## Core
59
-
60
- - [ ] `mm-harness status`, `doctor`, `actions`, `flows`, and a safe recipe run work
61
- without browser/device assumptions.
62
-
63
- Record the command, version, checkout SHA, visible mismatch, exit code, and JSON
64
- error envelope for any failure.
@@ -1,74 +0,0 @@
1
- # Video demo validation
2
-
3
- Use this checklist against the installed release before recording. Each checkout
4
- auto-detects its platform, ports, and slot; the paths below are placeholders only.
5
-
6
- ```bash
7
- export EXTENSION_CHECKOUT=/path/to/metamask-extension
8
- export MOBILE_CHECKOUT=/path/to/metamask-mobile
9
- export CORE_CHECKOUT=/path/to/core
10
- ```
11
-
12
- ## Common
13
-
14
- - [ ] `mm-harness --version` prints the intended release.
15
- - [ ] `mm-harness doctor` explains any unavailable capability and gives one next step.
16
- - [ ] Recipe results print diagnostics and absolute artifact paths inline.
17
- - [ ] `git status --short` is unchanged in each product checkout after validation.
18
-
19
- ## Extension
20
-
21
- ```bash
22
- cd "$EXTENSION_CHECKOUT"
23
- mm-harness launch --build --verify
24
- mm-harness doctor
25
- mm-harness actions positions
26
- mm-harness flows
27
- mm-harness run runner.smoke --artifacts-dir temp/demo
28
- mm-harness run wallet.smoke --artifacts-dir temp/wallet-demo
29
- mm-harness logs --source app
30
- mm-harness debug
31
- ```
32
-
33
- - [ ] Launch keeps an incremental webpack watcher alive.
34
- - [ ] Two small source edits compile incrementally; browser refresh shows both changes.
35
- - [ ] `debug` connects to the active slot's MetaMask target and opens Console.
36
- - [ ] `logs --source app` shows extension logs; `--source dapp` and `--source webpack`
37
- show their separate streams.
38
- - [ ] Each recipe prints `diagnostics: CLEAN` or expands every warning/error inline.
39
- - [ ] Each recipe prints absolute paths for its report, trace, diagnostics, screenshots,
40
- and artifact manifest.
41
-
42
- ## Mobile
43
-
44
- ```bash
45
- cd "$MOBILE_CHECKOUT"
46
- mm-harness launch ios --verify
47
- mm-harness fixtures set
48
- mm-harness call metamask.wallet.ensure_unlocked
49
- mm-harness status
50
- mm-harness logs --source app
51
- ```
52
-
53
- - [ ] The intended simulator is running; focus changes only when explicitly requested.
54
- - [ ] Fixture setup reads the canonical checkout fixture and unlocks the wallet.
55
- - [ ] Status reports the live route, wallet state, account, and fixture readiness.
56
- - [ ] App logs are separate from Metro logs (`mm-harness logs --source metro`).
57
-
58
- ## Core
59
-
60
- ```bash
61
- cd "$CORE_CHECKOUT"
62
- mm-harness doctor
63
- mm-harness run runner.smoke --artifacts-dir temp/demo
64
- ```
65
-
66
- - [ ] Doctor reports the headless controller ready.
67
- - [ ] The canonical wallet fixture supplies the default account.
68
- - [ ] Evidence paths are absolute; diagnostics is explicitly `N/A` for headless Core.
69
-
70
- ## Recording path
71
-
72
- Show the Extension discovery and proof commands, then the equivalent Mobile and Core
73
- commands. The useful story is: one auto-detected CLI, reusable team libraries,
74
- parameterized recipes, app diagnostics, and proof artifacts that a human can inspect.
@@ -1,27 +0,0 @@
1
- {
2
- "schema_version": 1,
3
- "title": "MetaMask Core runner smoke",
4
- "description": "Proves the headless Core runner resolves the checkout, executes generic actions, and writes a valid artifact package.",
5
- "validate": {
6
- "workflow": {
7
- "entry": "status",
8
- "nodes": {
9
- "status": {
10
- "action": "app.status",
11
- "next": "package",
12
- "intent": "Resolve the Core checkout and report headless readiness"
13
- },
14
- "package": {
15
- "action": "assert_file",
16
- "path": "package.json",
17
- "next": "done",
18
- "intent": "Prove generic project-file assertions work in Core"
19
- },
20
- "done": {
21
- "action": "end",
22
- "status": "pass"
23
- }
24
- }
25
- }
26
- }
27
- }
@@ -1,42 +0,0 @@
1
- {
2
- "schema_version": 1,
3
- "title": "MetaMask Extension wallet smoke",
4
- "description": "Fixture-backed proof that the Extension wallet is reachable, unlocked, readable, and visually capturable without depending on a product feature domain.",
5
- "validate": {
6
- "workflow": {
7
- "entry": "status",
8
- "nodes": {
9
- "status": {
10
- "action": "app.status",
11
- "next": "fixture",
12
- "intent": "Confirm the Extension runtime is reachable"
13
- },
14
- "fixture": {
15
- "action": "metamask.wallet.fixture_status",
16
- "next": "unlock",
17
- "intent": "Confirm the checkout wallet fixture is ready"
18
- },
19
- "unlock": {
20
- "action": "metamask.wallet.ensure_unlocked",
21
- "next": "state",
22
- "intent": "Ensure the fixture-backed wallet is unlocked"
23
- },
24
- "state": {
25
- "action": "metamask.wallet.read_state",
26
- "next": "screenshot",
27
- "intent": "Read the current wallet state through the public adapter"
28
- },
29
- "screenshot": {
30
- "action": "ui.screenshot",
31
- "path": "screenshots/wallet-smoke.png",
32
- "next": "done",
33
- "intent": "Capture visual wallet evidence"
34
- },
35
- "done": {
36
- "action": "end",
37
- "status": "pass"
38
- }
39
- }
40
- }
41
- }
42
- }
@@ -1,42 +0,0 @@
1
- {
2
- "schema_version": 1,
3
- "title": "MetaMask Mobile wallet smoke",
4
- "description": "Fixture-backed proof that the Mobile wallet is reachable, unlocked, readable, and visually capturable without depending on a product feature domain.",
5
- "validate": {
6
- "workflow": {
7
- "entry": "status",
8
- "nodes": {
9
- "status": {
10
- "action": "app.status",
11
- "next": "fixture",
12
- "intent": "Confirm the Mobile runtime is reachable"
13
- },
14
- "fixture": {
15
- "action": "metamask.wallet.fixture_status",
16
- "next": "unlock",
17
- "intent": "Confirm the checkout wallet fixture is ready"
18
- },
19
- "unlock": {
20
- "action": "metamask.wallet.ensure_unlocked",
21
- "next": "state",
22
- "intent": "Ensure the fixture-backed wallet is unlocked"
23
- },
24
- "state": {
25
- "action": "metamask.wallet.read_state",
26
- "next": "screenshot",
27
- "intent": "Read the current wallet state through the public adapter"
28
- },
29
- "screenshot": {
30
- "action": "ui.screenshot",
31
- "path": "screenshots/wallet-smoke.png",
32
- "next": "done",
33
- "intent": "Capture visual wallet evidence"
34
- },
35
- "done": {
36
- "action": "end",
37
- "status": "pass"
38
- }
39
- }
40
- }
41
- }
42
- }