@deeeed/metamask-harness 0.17.1 → 0.17.3
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.
- package/CHANGELOG.md +36 -10
- package/README.md +12 -6
- package/adapters/core/cleanup.sh +0 -0
- package/adapters/core/inject.sh +0 -0
- package/adapters/extension/cleanup.mjs +0 -0
- package/adapters/extension/ensure-browser.sh +0 -0
- package/adapters/extension/inject.mjs +0 -0
- package/adapters/extension/launch-browser.cjs +0 -0
- package/adapters/extension/launch.sh +0 -0
- package/adapters/extension/live.sh +0 -0
- package/adapters/extension/readiness.mjs +0 -0
- package/adapters/extension/reattach.sh +0 -0
- package/adapters/extension/refresh-build.sh +0 -0
- package/adapters/extension/seed-fixture.sh +0 -0
- package/adapters/extension/sidepanel-toggle.sh +0 -0
- package/adapters/extension/snapshot-dist.sh +0 -0
- package/adapters/extension/start-watch.sh +1 -0
- package/adapters/extension/stop-viewers.sh +0 -0
- package/adapters/extension/sync-webpack-dist.cjs +1 -1
- package/adapters/extension/verify.sh +0 -0
- package/adapters/extension/wallet-fixture-state.cjs +0 -0
- package/adapters/manifest.json +9 -1
- package/adapters/mobile/bridge-runtime/cdp-bridge.cjs +0 -0
- package/adapters/mobile/bridge-runtime/lib/match-bridge-target.cjs +3 -1
- package/adapters/mobile/bridge-runtime/setup-wallet.sh +0 -0
- package/adapters/mobile/cleanup.sh +0 -0
- package/adapters/mobile/inject.sh +13 -11
- package/adapters/mobile/launch-console-forwarder.cjs +0 -0
- package/adapters/mobile/lib/metro-listener.sh +0 -0
- package/adapters/mobile/lib/tmux-viewer.sh +0 -0
- package/adapters/mobile/open-device.sh +0 -0
- package/adapters/mobile/prewarm-bundle.sh +0 -0
- package/adapters/mobile/start-console-forwarder.sh +3 -3
- package/adapters/mobile/start-metro.sh +0 -0
- package/adapters/mobile/stop-metro.sh +0 -0
- package/adapters/mobile/verify.sh +0 -0
- package/adapters/mobile/wait-for-bridge.sh +22 -8
- package/adapters/mobile/yarn-setup.sh +0 -0
- package/adapters/shared/activate-repo-node.sh +0 -0
- package/adapters/shared/activate-repo-ruby.sh +0 -0
- package/adapters/shared/cli-ux.sh +0 -0
- package/adapters/shared/ensure-runner-deps.sh +0 -0
- package/adapters/shared/harness-path.sh +0 -0
- package/adapters/shared/hash-helpers.sh +0 -0
- package/adapters/shared/install-repo-deps.sh +0 -0
- package/adapters/shared/json-field.sh +0 -0
- package/adapters/shared/open-debug.mjs +7 -1
- package/adapters/shared/open-log-window.sh +0 -0
- package/adapters/shared/reap-checkout-metros.sh +0 -0
- package/adapters/shared/resolve-slot-ports.mjs +0 -0
- package/adapters/shared/resolve-slot-ports.sh +0 -0
- package/adapters/shared/sync-wallet-fixture.sh +0 -0
- package/adapters/shared/tmux-session.sh +0 -0
- package/adapters/shared/tmux-viewer.sh +0 -0
- package/adapters/shared/update-check-worker.mjs +35 -0
- package/dist/adapters/extension/console-capture.js +10 -1
- package/dist/adapters/mobile/prepare.js +5 -5
- package/dist/adapters/slot-ports.js +8 -0
- package/dist/cli.js +1 -1
- package/dist/commands/doctor.js +33 -8
- package/dist/commands/run-engine.js +2 -4
- package/dist/commands/update.js +16 -28
- package/dist/harness.js +24 -9
- package/dist/mm-harness-cli.js +8 -7
- package/dist/run-diagnostics.js +1 -6
- package/docs/ADAPTER-SURFACE.md +19 -112
- package/docs/CLI-ERGONOMICS-HUMAN-QA.md +16 -0
- package/docs/UX-PRINCIPLES.md +1 -1
- package/docs/VIDEO-DEMO-VALIDATION.md +56 -44
- package/docs/recipe-libraries.md +45 -275
- package/library/README.md +4 -0
- package/library/manifests/core.action-manifest.json +7 -7
- package/library/recipes/runner/smoke.core.recipe.json +27 -0
- package/library/recipes/wallet/smoke.extension.recipe.json +42 -0
- package/library/recipes/wallet/smoke.mobile.recipe.json +42 -0
- package/package.json +4 -2
- package/scripts/completions.sh +0 -0
- package/scripts/install-completions.sh +0 -0
- package/scripts/validate-human-outcomes.mjs +169 -0
package/dist/commands/update.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { spawnSync } from "node:child_process";
|
|
1
|
+
import { spawn, spawnSync } from "node:child_process";
|
|
2
2
|
import fs from "node:fs";
|
|
3
3
|
import os from "node:os";
|
|
4
4
|
import path from "node:path";
|
|
@@ -6,7 +6,8 @@ import { runnerDir } from "../paths.js";
|
|
|
6
6
|
import { EXIT, flag, parseFlags } from "./shared.js";
|
|
7
7
|
const PACKAGE_NAME = "@deeeed/metamask-harness";
|
|
8
8
|
const NUDGE_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
9
|
-
const NUDGE_FETCH_TIMEOUT_MS =
|
|
9
|
+
const NUDGE_FETCH_TIMEOUT_MS = 2e3;
|
|
10
|
+
const REGISTRY_DIST_TAGS_ENDPOINT = `https://registry.npmjs.org/-/package/${encodeURIComponent(PACKAGE_NAME)}/dist-tags`;
|
|
10
11
|
function currentVersion() {
|
|
11
12
|
try {
|
|
12
13
|
const pkg = JSON.parse(fs.readFileSync(path.join(runnerDir, "package.json"), "utf8"));
|
|
@@ -56,24 +57,6 @@ function fetchLatest(timeoutMs) {
|
|
|
56
57
|
if (!latest) return { error: { kind: "unreachable", message: "npm returned no dist-tag for latest" } };
|
|
57
58
|
return { latest };
|
|
58
59
|
}
|
|
59
|
-
async function fetchLatestAsync(timeoutMs) {
|
|
60
|
-
const controller = new AbortController();
|
|
61
|
-
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
62
|
-
try {
|
|
63
|
-
const url = `https://registry.npmjs.org/-/package/${encodeURIComponent(PACKAGE_NAME)}/dist-tags`;
|
|
64
|
-
const res = await fetch(url, { signal: controller.signal });
|
|
65
|
-
if (!res.ok) return { error: { kind: "unreachable", message: `registry returned HTTP ${res.status}` } };
|
|
66
|
-
const data = await res.json();
|
|
67
|
-
const latest = data["latest"];
|
|
68
|
-
if (!latest) return { error: { kind: "unreachable", message: "npm registry returned no latest dist-tag" } };
|
|
69
|
-
return { latest };
|
|
70
|
-
} catch (err) {
|
|
71
|
-
const msg = err.name === "AbortError" ? `registry fetch timed out after ${timeoutMs}ms` : err.message;
|
|
72
|
-
return { error: { kind: "unreachable", message: msg } };
|
|
73
|
-
} finally {
|
|
74
|
-
clearTimeout(timer);
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
60
|
function teachFailure(json, current, code, message, userAction) {
|
|
78
61
|
if (json) {
|
|
79
62
|
console.log(
|
|
@@ -211,23 +194,28 @@ function nudgeDisabled() {
|
|
|
211
194
|
function nudgeLine(current, latest) {
|
|
212
195
|
return isNewer(latest, current) ? `mm-harness ${current} \u2192 ${latest} available \xB7 run: mm-harness update` : null;
|
|
213
196
|
}
|
|
214
|
-
|
|
197
|
+
function maybeNudge(now = Date.now()) {
|
|
215
198
|
if (nudgeDisabled()) return;
|
|
216
199
|
const file = cacheFile();
|
|
217
200
|
const cache = readCache(file);
|
|
218
201
|
const current = currentVersion();
|
|
219
|
-
let latest = cache?.latest ?? "";
|
|
220
202
|
process.once("exit", () => {
|
|
221
|
-
const line = nudgeLine(current, latest);
|
|
203
|
+
const line = nudgeLine(current, cache?.latest ?? "");
|
|
222
204
|
if (line) process.stderr.write(`${line}
|
|
223
205
|
`);
|
|
224
206
|
});
|
|
225
207
|
if (!cache || now - cache.lastCheck >= NUDGE_INTERVAL_MS) {
|
|
226
|
-
writeCache(file, { lastCheck: now, latest });
|
|
227
|
-
const
|
|
228
|
-
if (
|
|
229
|
-
|
|
230
|
-
|
|
208
|
+
writeCache(file, { lastCheck: now, latest: cache?.latest ?? "" });
|
|
209
|
+
const worker = path.join(runnerDir, "adapters", "shared", "update-check-worker.mjs");
|
|
210
|
+
if (!fs.existsSync(worker)) return;
|
|
211
|
+
try {
|
|
212
|
+
const child = spawn(
|
|
213
|
+
process.execPath,
|
|
214
|
+
[worker, file, String(NUDGE_FETCH_TIMEOUT_MS), REGISTRY_DIST_TAGS_ENDPOINT],
|
|
215
|
+
{ detached: true, stdio: "ignore" }
|
|
216
|
+
);
|
|
217
|
+
child.unref();
|
|
218
|
+
} catch {
|
|
231
219
|
}
|
|
232
220
|
}
|
|
233
221
|
}
|
package/dist/harness.js
CHANGED
|
@@ -71,6 +71,7 @@ function parseHarnessArgs(args) {
|
|
|
71
71
|
const forward = [];
|
|
72
72
|
let adapter;
|
|
73
73
|
let json = false;
|
|
74
|
+
let quiet = false;
|
|
74
75
|
let separator = false;
|
|
75
76
|
for (let i = 0; i < args.length; i += 1) {
|
|
76
77
|
const arg = args[i];
|
|
@@ -86,6 +87,10 @@ function parseHarnessArgs(args) {
|
|
|
86
87
|
json = true;
|
|
87
88
|
continue;
|
|
88
89
|
}
|
|
90
|
+
if (arg === "--quiet") {
|
|
91
|
+
quiet = true;
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
89
94
|
if (arg === "--adapter" || arg === "--platform") {
|
|
90
95
|
const value = args[i + 1];
|
|
91
96
|
if (arg === "--adapter") {
|
|
@@ -116,7 +121,7 @@ function parseHarnessArgs(args) {
|
|
|
116
121
|
}
|
|
117
122
|
forward.push(arg);
|
|
118
123
|
}
|
|
119
|
-
return { adapter, json, forward };
|
|
124
|
+
return { adapter, json, quiet, forward };
|
|
120
125
|
}
|
|
121
126
|
const ADAPTER_DETECT_NEXT = "cd into a MetaMask checkout or pass --target <path>, or force it with --adapter <mobile|extension|core>";
|
|
122
127
|
function detectAdapter(target) {
|
|
@@ -276,12 +281,14 @@ async function handleHarness(argv) {
|
|
|
276
281
|
return 2;
|
|
277
282
|
}
|
|
278
283
|
const harnessAction = action;
|
|
279
|
-
const { adapter: parsedAdapter, json, forward } = parseHarnessArgs(argv.slice(1));
|
|
284
|
+
const { adapter: parsedAdapter, json, quiet, forward } = parseHarnessArgs(argv.slice(1));
|
|
280
285
|
const rawTarget = argValue(forward, "--target");
|
|
281
286
|
const target = path.resolve(rawTarget ?? process.cwd());
|
|
282
287
|
const adapter = parsedAdapter ?? detectAdapter(target);
|
|
283
288
|
if (!adapter || !isAdapter(adapter)) {
|
|
284
|
-
if (
|
|
289
|
+
if (quiet) {
|
|
290
|
+
return 2;
|
|
291
|
+
} else if (json) {
|
|
285
292
|
const detectError = adapter ? { code: "UNSUPPORTED_PLATFORM", message: `unsupported platform: ${adapter}`, userAction: "pass --adapter <mobile|extension|core> to specify a supported adapter" } : { code: "ADAPTER_DETECTION_FAILED", message: `could not detect the MetaMask repo type for ${target}`, userAction: ADAPTER_DETECT_NEXT };
|
|
286
293
|
console.log(harnessSummary(harnessAction, void 0, target, "fail", 2, false, detectError));
|
|
287
294
|
} else {
|
|
@@ -305,7 +312,9 @@ async function handleHarness(argv) {
|
|
|
305
312
|
}
|
|
306
313
|
const dispatch = resolveHarnessDispatch(adapter, harnessAction, target);
|
|
307
314
|
if ("error" in dispatch) {
|
|
308
|
-
if (
|
|
315
|
+
if (quiet) {
|
|
316
|
+
return 1;
|
|
317
|
+
} else if (json) {
|
|
309
318
|
console.log(
|
|
310
319
|
harnessSummary(harnessAction, adapter, target, "fail", 1, autoDetected, {
|
|
311
320
|
code: "DISPATCH_UNAVAILABLE",
|
|
@@ -321,14 +330,16 @@ async function handleHarness(argv) {
|
|
|
321
330
|
}
|
|
322
331
|
return 1;
|
|
323
332
|
}
|
|
324
|
-
if (!json) {
|
|
333
|
+
if (!json && !quiet) {
|
|
325
334
|
const detected = autoDetected ? ", auto-detected" : "";
|
|
326
335
|
console.error(`\u2192 ${harnessAction} (${adapter}${detected}) \u2014 target: ${target}`);
|
|
327
336
|
}
|
|
328
337
|
const start = Date.now();
|
|
329
338
|
if (shellLeafMissing(dispatch.command)) {
|
|
330
339
|
const message = missingShellLeafMessage(dispatch.command);
|
|
331
|
-
if (
|
|
340
|
+
if (quiet) {
|
|
341
|
+
return 1;
|
|
342
|
+
} else if (json) {
|
|
332
343
|
console.log(
|
|
333
344
|
harnessSummary(harnessAction, adapter, target, "fail", 1, autoDetected, {
|
|
334
345
|
code: "HARNESS_SPAWN_FAILED",
|
|
@@ -348,12 +359,14 @@ async function handleHarness(argv) {
|
|
|
348
359
|
const result = spawnSync(dispatchBin, dispatchArgs, {
|
|
349
360
|
// Default: stream child output verbatim (byte-identical to the skill path).
|
|
350
361
|
// --json: route child output to our stderr so stdout carries only the summary.
|
|
351
|
-
stdio: json ? ["inherit", 2, "inherit"] : "inherit",
|
|
362
|
+
stdio: quiet ? "ignore" : json ? ["inherit", 2, "inherit"] : "inherit",
|
|
352
363
|
env: process.env
|
|
353
364
|
});
|
|
354
365
|
const seconds = ((Date.now() - start) / 1e3).toFixed(1);
|
|
355
366
|
if (result.error) {
|
|
356
|
-
if (
|
|
367
|
+
if (quiet) {
|
|
368
|
+
return 1;
|
|
369
|
+
} else if (json) {
|
|
357
370
|
console.log(
|
|
358
371
|
harnessSummary(harnessAction, adapter, target, "fail", 1, autoDetected, {
|
|
359
372
|
code: "HARNESS_SPAWN_FAILED",
|
|
@@ -368,7 +381,9 @@ async function handleHarness(argv) {
|
|
|
368
381
|
return 1;
|
|
369
382
|
}
|
|
370
383
|
const exitCode = result.status ?? 1;
|
|
371
|
-
if (
|
|
384
|
+
if (quiet) {
|
|
385
|
+
return exitCode;
|
|
386
|
+
} else if (json) {
|
|
372
387
|
console.log(
|
|
373
388
|
harnessSummary(
|
|
374
389
|
harnessAction,
|
package/dist/mm-harness-cli.js
CHANGED
|
@@ -76,11 +76,11 @@ Example:
|
|
|
76
76
|
},
|
|
77
77
|
{
|
|
78
78
|
name: "actions",
|
|
79
|
-
summary: "
|
|
79
|
+
summary: "Discover typed single operations and their fields.",
|
|
80
80
|
example: "mm-harness actions --adapter mobile",
|
|
81
81
|
helpText: `mm-harness actions [query] [flags]
|
|
82
82
|
|
|
83
|
-
|
|
83
|
+
Discover typed single operations and their field schemas for this checkout.
|
|
84
84
|
|
|
85
85
|
query Search names, categories, fields, and descriptions (typo-tolerant)
|
|
86
86
|
--action <name> Describe one action; fuzzy-resolves like call (short or full name)
|
|
@@ -152,11 +152,11 @@ Example (real actions; run mm-harness actions for this checkout's full set):
|
|
|
152
152
|
},
|
|
153
153
|
{
|
|
154
154
|
name: "flows",
|
|
155
|
-
summary: "Browse
|
|
155
|
+
summary: "Browse reusable parameterized action sequences.",
|
|
156
156
|
example: "mm-harness flows",
|
|
157
157
|
helpText: `mm-harness flows [flags]
|
|
158
158
|
|
|
159
|
-
Browse
|
|
159
|
+
Browse reusable parameterized action sequences. Flows resolve by precedence
|
|
160
160
|
(personal > team > canonical); the highest-tier copy wins and shadows lower ones.
|
|
161
161
|
Flow resolution is adapter-global, so there is no --adapter flag here.
|
|
162
162
|
|
|
@@ -169,11 +169,12 @@ Example:
|
|
|
169
169
|
},
|
|
170
170
|
{
|
|
171
171
|
name: "run",
|
|
172
|
-
summary: "
|
|
172
|
+
summary: "Execute a complete proof and write its evidence.",
|
|
173
173
|
example: "mm-harness run recipe.json",
|
|
174
174
|
helpText: `mm-harness run <recipe.json> [flags]
|
|
175
175
|
|
|
176
|
-
Validate
|
|
176
|
+
Validate and execute a complete proof, then write summary, trace, diagnostics,
|
|
177
|
+
and artifacts. A recipe may use actions directly or call reusable flows.
|
|
177
178
|
|
|
178
179
|
--list List everything invocable for the adapter (actions + flows); no <recipe> needed
|
|
179
180
|
--plan Validate + print execution plan, touching nothing. Exit 5 if invalid.
|
|
@@ -696,7 +697,7 @@ program.command("completions").description("Install/print bundled shell tab-comp
|
|
|
696
697
|
});
|
|
697
698
|
const NUDGE_SKIP = ["update", "completions", "completion-candidates"];
|
|
698
699
|
if (rawArgv.length > 0 && !NUDGE_SKIP.includes(rawArgv[0])) {
|
|
699
|
-
|
|
700
|
+
maybeNudge();
|
|
700
701
|
}
|
|
701
702
|
function hasPassthroughHelp(argv) {
|
|
702
703
|
const divider = argv.indexOf("--");
|
package/dist/run-diagnostics.js
CHANGED
|
@@ -52,7 +52,6 @@ function isRunDiagnosticsDocument(value) {
|
|
|
52
52
|
function finishRunDiagnostics(baseline, result) {
|
|
53
53
|
if (!baseline) return result;
|
|
54
54
|
try {
|
|
55
|
-
stopRunDiagnostics(baseline);
|
|
56
55
|
const bufferedIssues = baseline.mobileIssueBuffer ? collectMobileIssueBuffer(baseline.mobileIssueBuffer.projectRoot) : void 0;
|
|
57
56
|
const diagnostics = collectRunDiagnostics(baseline, bufferedIssues);
|
|
58
57
|
const artifactsDir = path.dirname(result.summaryPath);
|
|
@@ -83,9 +82,6 @@ function finishRunDiagnostics(baseline, result) {
|
|
|
83
82
|
};
|
|
84
83
|
}
|
|
85
84
|
}
|
|
86
|
-
function stopRunDiagnostics(baseline) {
|
|
87
|
-
void baseline;
|
|
88
|
-
}
|
|
89
85
|
function collectRunDiagnostics(baseline, bufferedIssues) {
|
|
90
86
|
const stat = safeStat(baseline.source.path);
|
|
91
87
|
const source = {
|
|
@@ -261,6 +257,5 @@ export {
|
|
|
261
257
|
collectRunDiagnostics,
|
|
262
258
|
finishRunDiagnostics,
|
|
263
259
|
formatRunDiagnosticsForHuman,
|
|
264
|
-
readRunDiagnosticsDocument
|
|
265
|
-
stopRunDiagnostics
|
|
260
|
+
readRunDiagnosticsDocument
|
|
266
261
|
};
|
package/docs/ADAPTER-SURFACE.md
CHANGED
|
@@ -1,119 +1,26 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Adapter surface
|
|
2
2
|
|
|
3
|
-
`
|
|
4
|
-
|
|
5
|
-
own `if (adapter === 'mobile') … else if (adapter === 'extension') …` ladder.
|
|
6
|
-
When it does, a platform gets forgotten in one command but not another — the
|
|
7
|
-
class of bug where `doctor` reported a live runtime section for mobile only,
|
|
8
|
-
`stop` refused every non-mobile adapter, and `logs` knew a single extension log
|
|
9
|
-
file instead of all three. Those are *platform-blind* commands.
|
|
3
|
+
`src/adapters/surface.ts` is the typed seam for Mobile, Extension, and Core.
|
|
4
|
+
Commands ask it for platform behavior instead of re-deriving platform rules.
|
|
10
5
|
|
|
11
|
-
|
|
12
|
-
a registry, that owns the behavior a command would otherwise branch on.
|
|
6
|
+
Each adapter implements:
|
|
13
7
|
|
|
14
|
-
|
|
8
|
+
- runtime readiness;
|
|
9
|
+
- slot-owned ports;
|
|
10
|
+
- dev-server lifecycle;
|
|
11
|
+
- log sources;
|
|
12
|
+
- launch/recovery hints;
|
|
13
|
+
- headless capability.
|
|
15
14
|
|
|
16
|
-
`src/adapters/surface.ts
|
|
17
|
-
|
|
15
|
+
Implementations live in `src/adapters/{mobile,extension,core}/surface.ts`.
|
|
16
|
+
Platform-specific shell and Node operations live under `adapters/<platform>/`.
|
|
18
17
|
|
|
19
|
-
|
|
20
|
-
- `src/adapters/extension/surface.ts`
|
|
21
|
-
- `src/adapters/core/surface.ts`
|
|
18
|
+
When adding behavior:
|
|
22
19
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
resolveSlotPorts(target: string): void; // context > pool > formula (no-op for core)
|
|
28
|
-
runtimeStatus(target: string): Promise<AdapterRuntimeStatus>; // read-only readiness for doctor
|
|
29
|
-
devServer: {
|
|
30
|
-
describe(): string; // "Metro" | "webpack watcher" | "no dev server (headless)"
|
|
31
|
-
stop(target: string): AdapterDevServerStop; // idempotent, slot-scoped; headless = teach
|
|
32
|
-
};
|
|
33
|
-
logSources(target: string): AdapterLogSource[]; // ordered candidate log files (empty for core)
|
|
34
|
-
hints: { launch: string; relaunch: string }; // platform-phrased Next: hints
|
|
35
|
-
}
|
|
36
|
-
```
|
|
20
|
+
1. Extend `AdapterSurface` only when every platform needs the concept.
|
|
21
|
+
2. Implement all three adapters; Core should explicitly report headless/N/A.
|
|
22
|
+
3. Call the surface from the command instead of adding adapter conditionals.
|
|
23
|
+
4. Add a registry-completeness contract and a real affected-platform check.
|
|
37
24
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
```ts
|
|
42
|
-
interface AdapterRuntimeStatus {
|
|
43
|
-
decision: string;
|
|
44
|
-
reasonCode?: string;
|
|
45
|
-
reasons: string[];
|
|
46
|
-
deps?: string;
|
|
47
|
-
devServer?: { label: string; status: string }; // absent for headless core
|
|
48
|
-
}
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
The implementations are thin: they delegate to the readiness/port plumbing that
|
|
52
|
-
already existed (`decideExtensionReadiness`, `mobileRuntimeStatus`, the slot-port
|
|
53
|
-
resolvers and the webpack-watcher stop, all re-homed to
|
|
54
|
-
`src/adapters/slot-ports.ts`). The surface is an organizing seam, not a rewrite.
|
|
55
|
-
|
|
56
|
-
## The rule
|
|
57
|
-
|
|
58
|
-
**A command never branches on adapter for behavior the surface owns.**
|
|
59
|
-
|
|
60
|
-
- Do not write `if (adapter === 'core')` — ask `surface.headless`.
|
|
61
|
-
- Do not write `adapter === 'mobile' ? metroPorts() : extensionPorts()` — call
|
|
62
|
-
`surface.resolveSlotPorts(target)`.
|
|
63
|
-
- Do not print `adapter === 'mobile' ? 'mm-harness launch ios' : 'mm-harness
|
|
64
|
-
launch'` — use `surface.hints.launch`.
|
|
65
|
-
|
|
66
|
-
A new platform behavior is added by **extending the surface** (a new member on
|
|
67
|
-
the interface plus its three implementations), never by adding another branch to
|
|
68
|
-
a command. TypeScript then makes it impossible to ship a platform that forgot the
|
|
69
|
-
new member, because each implementation is annotated `: AdapterSurface`.
|
|
70
|
-
|
|
71
|
-
What the surface deliberately does **not** own: a command may still branch on
|
|
72
|
-
adapter for a *mechanism* that is genuinely platform-specific and not part of the
|
|
73
|
-
interface — e.g. `fixtures set` uses a shell arm on mobile and the engine path on
|
|
74
|
-
extension. Those branches select a mechanism; they never re-derive readiness,
|
|
75
|
-
ports, log locations, dev-server lifecycle, or Next: phrasing, which are the
|
|
76
|
-
surface's responsibility.
|
|
77
|
-
|
|
78
|
-
## Migration status
|
|
79
|
-
|
|
80
|
-
Every command that used to branch on adapter now resolves through the surface:
|
|
81
|
-
|
|
82
|
-
| command | surface-backed | uses |
|
|
83
|
-
| --- | --- | --- |
|
|
84
|
-
| `doctor` | yes | `resolveSlotPorts` + `runtimeStatus` (mobile/extension/core runtime section) |
|
|
85
|
-
| `launch` | yes | `resolveSlotPorts` |
|
|
86
|
-
| `stop` | yes | `resolveSlotPorts` + `devServer.stop` (+ headless teaching) |
|
|
87
|
-
| `logs` | yes | `logSources` + `hints.launch` + `headless` |
|
|
88
|
-
| `debug` | yes | `headless` + `hints.relaunch` (core teaching); flag semantics stay per-command |
|
|
89
|
-
| `fixtures` | yes | `headless` + `hints` (retry/launch); the set *mechanism* stays per-platform |
|
|
90
|
-
| `run` / `call` | n/a | engine path; core-headless is handled by the heal contract, not the surface |
|
|
91
|
-
| `flows` | no (by design) | recipe-library flows are adapter-global; not a platform-owned behavior |
|
|
92
|
-
| `completion-candidates` | partial | `actions` scope to the detected checkout adapter; `flows` are adapter-global |
|
|
93
|
-
|
|
94
|
-
`flows` is intentionally not surface-backed: a recipe-library flow can compose
|
|
95
|
-
actions across platforms, so flows are adapter-global by design rather than a
|
|
96
|
-
platform-blind gap. `completion-candidates actions` already resolves the adapter
|
|
97
|
-
from the checkout context (cwd/`--target`); only the adapter-global `flows`
|
|
98
|
-
candidates are unscoped, matching the flows model.
|
|
99
|
-
|
|
100
|
-
## How this composes with the UX principles
|
|
101
|
-
|
|
102
|
-
`docs/UX-PRINCIPLES.md` principle 1 (*Context-aware by default* — "output is
|
|
103
|
-
scoped to the platform… help, flag lists, and completion candidates shrink to
|
|
104
|
-
what applies here") states the intent. The adapter surface is its **enforcement
|
|
105
|
-
mechanism**: a command that resolves platform behavior through the surface is
|
|
106
|
-
context-aware by construction, and one that hand-rolls an adapter ladder is the
|
|
107
|
-
exact failure principle 1 warns against. When adding a command or output path,
|
|
108
|
-
satisfy principle 1 by going through the surface.
|
|
109
|
-
|
|
110
|
-
## Adding a platform behavior — checklist
|
|
111
|
-
|
|
112
|
-
1. Add the member to `AdapterSurface` in `src/adapters/surface.ts`.
|
|
113
|
-
2. Implement it in all three `src/adapters/*/surface.ts` (TypeScript will not
|
|
114
|
-
compile until you do).
|
|
115
|
-
3. Have the command call `getAdapterSurface(adapter).<member>` instead of
|
|
116
|
-
branching.
|
|
117
|
-
4. Cover the closed blind spot with a contract test (see
|
|
118
|
-
`tests/contract/adapter-surface.test.sh`), and keep the registry-completeness
|
|
119
|
-
assertion green.
|
|
25
|
+
Mechanisms unique to one command may remain platform-specific. Shared readiness,
|
|
26
|
+
ports, logs, lifecycle, and recovery language belong on the surface.
|
|
@@ -3,11 +3,27 @@
|
|
|
3
3
|
Run this from each product checkout before a release. Verify the visible outcome,
|
|
4
4
|
not only the exit code.
|
|
5
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
|
+
|
|
6
20
|
## Common
|
|
7
21
|
|
|
8
22
|
- [ ] `mm-harness --version` prints the candidate version.
|
|
9
23
|
- [ ] `mm-harness doctor` detects the correct product and reports only current
|
|
10
24
|
configuration.
|
|
25
|
+
- [ ] Without `capture-helper`, `doctor` names the CDP or simulator/ADB screenshot
|
|
26
|
+
fallback and says video is unavailable.
|
|
11
27
|
- [ ] A misspelled flag explains the error and gives a runnable next command.
|
|
12
28
|
- [ ] `mm-harness actions` and `mm-harness flows` return useful, bounded discovery.
|
|
13
29
|
- [ ] `mm-harness fixtures set` either applies the fixture or names the missing
|
package/docs/UX-PRINCIPLES.md
CHANGED
|
@@ -5,7 +5,7 @@ a PR that adds a command or output path is checked against each one.
|
|
|
5
5
|
|
|
6
6
|
**Scope: the HUMAN surface.** The agent contract is exempt and inviolable: under
|
|
7
7
|
`--json`, stdout carries exactly one machine-readable envelope — no color, no
|
|
8
|
-
banners, no decorative lines
|
|
8
|
+
banners, no decorative lines. Progress/streaming exists
|
|
9
9
|
for agents too, but only ever on stderr. When a rule below conflicts with envelope
|
|
10
10
|
purity, the envelope wins.
|
|
11
11
|
|
|
@@ -1,62 +1,74 @@
|
|
|
1
1
|
# Video demo validation
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
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
|
+
```
|
|
5
11
|
|
|
6
12
|
## Common
|
|
7
13
|
|
|
8
|
-
- [
|
|
9
|
-
- [
|
|
10
|
-
- [
|
|
11
|
-
- [
|
|
12
|
-
- [x] `doctor` reports current runtime fields only.
|
|
13
|
-
- [x] Recipe results print diagnostics and absolute artifact paths inline.
|
|
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.
|
|
14
18
|
|
|
15
|
-
## Extension
|
|
19
|
+
## Extension
|
|
16
20
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
-
|
|
20
|
-
|
|
21
|
-
-
|
|
22
|
-
-
|
|
23
|
-
|
|
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
|
+
```
|
|
24
32
|
|
|
25
|
-
|
|
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.
|
|
26
41
|
|
|
27
|
-
|
|
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.
|
|
42
|
+
## Mobile
|
|
34
43
|
|
|
35
|
-
|
|
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
|
+
```
|
|
36
52
|
|
|
37
|
-
- [
|
|
38
|
-
- [
|
|
39
|
-
- [
|
|
40
|
-
- [
|
|
41
|
-
- [x] Evidence prints absolute paths; diagnostics correctly reports `N/A` for headless Core.
|
|
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`).
|
|
42
57
|
|
|
43
|
-
##
|
|
58
|
+
## Core
|
|
44
59
|
|
|
45
60
|
```bash
|
|
46
|
-
|
|
47
|
-
cd /Users/deeeed/farmslot/repos/mme-2
|
|
61
|
+
cd "$CORE_CHECKOUT"
|
|
48
62
|
mm-harness doctor
|
|
49
|
-
mm-harness actions positions
|
|
50
|
-
mm-harness flows
|
|
51
63
|
mm-harness run runner.smoke --artifacts-dir temp/demo
|
|
64
|
+
```
|
|
52
65
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
mm-harness fixtures set
|
|
57
|
-
mm-harness call metamask.wallet.ensure_unlocked
|
|
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.
|
|
58
69
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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.
|